Exercise 1 solution FLAGS = { 0: "READONLY", 1: "HIDDEN", 2: "COMPRESSED", 3: "ENCRYPTED", } RESERVED_MASK = 0b11110000 def decode_flags(byte): if not 0 <= byte <= 255: raise ValueError("not a byte") if byte & RESERVED_MASK: raise ValueError(f"reserved bits set: {byte & RESERVED_MASK:#04x}") return [name for bit, name in FLAGS.items() if byte & (1 << bit)] def encode_flags(names): byte = 0 by_name = {name: bit for bit, name in FLAGS.items()} for name in names: if name not in by_name: raise ValueError(f"unknown flag {name!r}") byte |= 1 << by_name[name] return byte b = encode_flags(["COMPRESSED", "READONLY"]) print(bin(b), decode_flags(b)) print(decode_flags(0)) for label, call in [ ("reserved", lambda: decode_flags(0b00100001)), ("unknown", lambda: encode_flags(["HIDDEN", "SECRET"])), ]: try: call() except ValueError as e: print(label, "->", e) ok = all(encode_flags(decode_flags(n)) == n for n in range(16)) print(ok) # 0b101 ['READONLY', 'COMPRESSED'] # [] # reserved -> reserved bits set: 0x20 # unknown -> unknown flag 'SECRET' # True (Run as written; the output matched the comments.) Explanation: Each flag is one bit, tested with AND against 1 << bit and set with OR. The names are listed in bit order, so the decoded list is always in the same order whatever order the flags were given in. The reserved bits (4 to 7) are checked first with a mask, and the message shows which of them were set. Decoding then encoding every value from 0 to 15 returns the starting value, which shows the two functions are exact inverses over the valid range.