Skip to content
← Back to explainers

Python explainer

Combine Permission Bits with Flag

About 3 min to read. Allow extra time to work through examples.

Name each permission bit, combine bits with |, tell an any-bit check from an all-bits check, and reject numbers that are not valid masks.

This article uses Python. You can make it your practice mode to browse related content across the site.

One user's permissions on a Unix file fit in three bits. Read is 4, write is 2 and execute is 1, so 6 means read and write. Code full of mode & 4 makes the reader decode every number. enum.Flag gives each bit a name and keeps the bit operations.

from enum import Flag, auto


class Permission(Flag):
    EXECUTE = auto()
    WRITE = auto()
    READ = auto()
    RW = READ | WRITE
    ALL = READ | WRITE | EXECUTE


print(Permission.EXECUTE.value, Permission.WRITE.value, Permission.READ.value)  # 1 2 4
print(Permission.READ | Permission.WRITE)  # Permission.RW
print([member.name for member in Permission(5)])  # ['EXECUTE', 'READ']
print([member.name for member in Permission])     # ['EXECUTE', 'WRITE', 'READ']

In a Flag, auto() gives each new member the next bit, so the values go 1, 2, 4 and so on. RW and ALL give names to combinations of those bits. Looping over a value lists the single bits it contains. Looping over the class lists only the single-bit members, in the order you declared them.

If you store the numbers on disk or send them to another program, write them out, as in READ = 4. Then reordering the class cannot change them.

Any bit, or all of them

flags = Permission.READ
print(bool(flags & Permission.RW))        # True, READ is one of RW's bits
print(Permission.RW in flags)             # False, WRITE is missing
print(Permission.READ in Permission.ALL)  # True

a & b keeps the bits that a and b share, and bool() of the result is True if they share any bit. a in b is True only when every bit of a is also in b. For a single bit, the two checks agree. For a combination such as RW they differ, so pick the one you mean. Permission(0) asks for no bits, so it is in every value.

Check the range before you convert

def from_octal(bits: int) -> Permission:
    if not 0 <= bits <= 7:
        raise ValueError(f"{bits} is not a 3-bit permission mask")
    return Permission(bits)


print(from_octal(6))   # Permission.RW
print(Permission(-1))  # Permission.ALL

Permission(8) raises ValueError on its own, because 8 is a bit the class does not have. Permission(-1) does not raise. Python reads -1 as "every bit set" and returns ALL. Check the range yourself, so that your function rejects negative numbers too.

from_octal takes an int, not a string of digits. The name comes from file modes such as chmod 640, where each octal digit is one three-bit group. There, 6 is the owner's read and write, 4 is the group's read, and 0 is nothing for everyone else.

Check your understanding A user has Permission.READ. Does bool(user & Permission.RW) mean they can read and write? Show answer

No. user & Permission.RW keeps the bits the two share, which here is READ, so bool() gives True. That check passes if the user has any bit of RW. To require both bits, write Permission.RW in user, which is False here.

Use IntFlag when other code expects integers

from enum import IntFlag


class WirePermission(IntFlag):
    READ = 4
    WRITE = 2


incoming: int = 4
print(WirePermission.READ == incoming)             # True
print(WirePermission.READ | WirePermission.WRITE)  # 6
print(type(WirePermission.READ + 1))               # <class 'int'>

An IntFlag member is also an int. It equals the matching integer and works with code that expects one. Bit operations give back an IntFlag, but arithmetic such as + gives a plain int. An IntFlag keeps unknown bits instead of raising, so WirePermission(8) works. Use Flag when permissions should stay apart from ordinary numbers, and IntFlag when an existing interface passes integers.

Going further

  • The enum.verify decorator checks a class when Python defines it. With UNIQUE it rejects two names with the same value. With NAMED_FLAGS it rejects a combination that uses a bit with no name of its own.
  • Flag raises on unknown bits and IntFlag keeps them. The boundary class argument picks another rule, such as dropping unknown bits.
  • In a plain Enum, auto() counts 1, 2, 3. In a StrEnum, it gives the member's name in lower case.
  • See the enum documentation.

step 50 of 55

Everyday Standard Library Tools

Up next Decode a Three-Bit Permission Mask