skip to content

What does `Perm.READ in perms` test when perms is an enum.Flag value?

level: middleimportance: should knowfreq 32%

answer

  1. Not identity, and not equality
  2. Think sets, not members
  3. Every bit on the left must be present
  4. Direction matters, so it is asymmetric
  5. Empty value is inside everything

basics

~20 s

It is a subset test: True when every bit of the left operand is set in the composite on the right. Since Python 3.11 a composite Flag member is also iterable and sized, so you can list the single flags it carries.

solid answer

~40 s

`Flag.__contains__` is defined as containment of bits, not identity of members, so `Perm.READ in perms` is `True` whenever the READ bit is set in `perms`, and `(Perm.READ | Perm.WRITE) in perms` is `True` only when *both* bits are. It is the readable spelling of the `perms & Perm.READ` mask. Two edges catch people: the zero-valued member is contained in everything, because it has no bits to be missing; and the right-hand operand must be a member of the same flag enum — `1 in perms` raises `TypeError` on 3.14 even for an `enum.IntFlag`. Since Python 3.11 a composite member also supports `len()` and iteration, yielding its single-bit members, which is how you turn one value back into a list of permissions.

code

python · 16 lines
python
from enum import Flag, auto

class Perm(Flag):
    READ = auto()
    WRITE = auto()
    EXEC = auto()

perms = Perm.READ | Perm.WRITE
print(Perm.READ in perms)                  # True
print((Perm.READ | Perm.EXEC) in perms)    # False - needs both
print(list(perms), len(perms))             # [READ, WRITE] 2

try:
    1 in perms
except TypeError as exc:
    print("TypeError:", exc)

go deeper

for a junior

Know that Perm.READ in perms asks whether that flag is present in a combined value, and that it is the readable alternative to masking with &.

for a middle

Explain that containment is a subset test over bits: all bits of the left operand must be set on the right, the empty value is inside everything, and iterating a composite since 3.11 yields its single-bit members.

for a senior

Demonstrate the judgement that stops bugs: in is all-of, a truthy & is any-of, == is exact, and a raw integer must be converted into a member before it is tested rather than compared loosely.

for a principal

Own the API shape — whether callers get a flag value at all, whether helpers named for all-of and any-of are the only sanctioned tests, and how permission checks stay reviewable when the flag set outgrows what one reader holds in their head.

## Containment means subset For ordinary `enum.Enum` classes, `in` is asked of the *class*: `Perm.READ in Perm` says "is this a member of this enum". `enum.Flag` adds a second, more interesting meaning — `in` asked of a *member*: ```python from enum import Flag, auto class Perm(Flag): READ = auto() WRITE = auto() EXEC = auto() perms = Perm.READ | Perm.WRITE Perm.READ in perms # True Perm.EXEC in perms # False (Perm.READ | Perm.WRITE) in perms # True (Perm.READ | Perm.EXEC) in perms # False ``` The rule is pure bit arithmetic: `other in self` is `True` iff every bit of `other` is also set in `self`. It is therefore a **subset** test, and it is asymmetric — a bigger value is not contained in a smaller one, even though the smaller is contained in the bigger. That makes `in` and the mask idiom equivalent for a single flag: `Perm.READ in perms` and `bool(perms & Perm.READ)` compute the same answer. They diverge in readability once the left side is itself a composite. `bool(perms & (Perm.READ | Perm.WRITE))` is `True` when *either* bit is present, because `&` returns whatever overlap exists and any non-zero overlap is truthy — a genuine source of bugs. `(Perm.READ | Perm.WRITE) in perms` demands *both*. When the question is "does this value carry all of these", use `in`; when it is "does it carry any of these", the mask is the right tool. Being able to state that difference out loud is most of what this question is probing. ## The zero member The value 0 is a legal `Flag` value — it is what `&` returns when nothing overlaps, and it can be given a name (`NONE = 0`). It has no bits, so *every* value contains it: ```python class Access(Flag): NONE = 0 READ = 1 WRITE = 2 Access.NONE in (Access.READ | Access.WRITE) # True Access.NONE in Access.NONE # True ``` Any code that loops over the members of a flag enum and asks `flag in value` will therefore report the zero member as "granted" every single time. This is not a bug in `enum`; it is the correct reading of subset. The practical consequences are to avoid naming a zero member when you intend to iterate over grants, and to remember that iterating a class skips it anyway — `list(Access)` yields `READ` and `WRITE` only, since a zero-valued member names no bit and is not canonical. ## `in` is strict about its left operand On Python 3.14, `Flag.__contains__` refuses anything that is not a member of the same flag class: ```python 1 in perms # TypeError: unsupported operand type(s) for 'in': 'int' and 'Perm' ``` The surprise is that this holds for `enum.IntFlag` too, whose members *are* integers and compare equal to them. `IntFlag` inherits the same `__contains__`, so `1 in some_int_flag_value` raises even though `some_int_flag_value == 1` may be `True`. If you have a raw integer, convert it first — `Access(1) in perms` — and let the conversion be the place where an invalid value is rejected. Note this is the member-level `in`; the class-level `Perm.READ in Perm` is a different check, and since Python 3.12 the class-level form also accepts a raw value (`3 in Perm`) and returns a bool instead of raising. ## Iteration and length Python 3.11 made composite flag members iterable and sized: ```python perms = Perm.READ | Perm.WRITE list(perms) # [<Perm.READ: 1>, <Perm.WRITE: 2>] len(perms) # 2 list(Perm(0)) # [] len(Perm(0)) # 0 ``` Iteration yields the canonical single-bit members contained in the value, in definition order, which is the clean way to render one stored value as a human-readable list, to log which permissions a request actually carried, or to fan a composite out into rows. Before 3.11 you had to do that by hand with a loop over the class and a mask, and a lot of code still does — recognising the older idiom and knowing it is now unnecessary is a good signal. ## Putting it together The three-way summary worth having ready: `==` is exact equality of the whole set, `in` is "contains all of these", and `&` plus `bool()` is "contains any of these". Nearly every real flag bug is one of those three used where another was meant.

  • Why is `Access.NONE in perms` always True for a zero-valued flag member?
    Containment is a subset test over bits, and the zero member has no bits, so nothing can be missing from `perms`. It is the empty set, which is a subset of everything — including itself. The practical advice is not to name a zero member if you plan to loop over the enum asking which flags a value carries, because that loop will report it as granted every time.
  • How would you check that a value carries *any* of several flags rather than all of them?
    Mask and test for truth: `bool(perms & (Perm.READ | Perm.EXEC))`. `&` keeps whatever overlap exists, and any non-zero overlap is truthy, so this is the any-of test. `in` is the all-of test. Mixing them up is the most common flag bug, and spelling both out in a helper named `has_any` / `has_all` is a reasonable defence.
  • Why does `1 in perms` raise on an `enum.IntFlag`, whose members are ints?
    `IntFlag` inherits `Flag.__contains__`, which requires the left operand to be a member of the same flag class regardless of the mixed-in `int` base — so the comparison raises `TypeError` on 3.14 even though `perms == 3` may be `True`. Convert the integer first (`Perm(1) in perms`) so an out-of-range value is rejected at the conversion rather than silently tested.

Reading in as a subset test is like checking a shopping list against a basket: it passes only when every item on the list is in the basket, and an empty list always passes.

saying these in an interview costs you the question

  • Saying `in` means equality of the two values
  • Claiming `in` on a composite tests any bit rather than all
  • Thinking a zero-valued flag member is contained in nothing
  • Expecting `1 in perms` to work on an IntFlag
  • Believing a composite member cannot be iterated
  • Treating containment as symmetric in either direction

context