enum.IntFlag Silently Accepts Undefined Bits, enum.Flag Raises (tested)

IntFlag and Flag look like interchangeable ways to define bitwise flag enums -- one is just also an int. We measured what happens when you construct each from an integer that sets bits beyond the ones you defined, the shape you'd see parsing a permissions bitmask from a database column or external API. One class raises immediately. The other lets it through silently.

1. Flag raises ValueError on an undefined bit

import enum

class Perm(enum.Flag):
    READ = 1
    WRITE = 2
    EXEC = 4

external = 13  # READ(1) + EXEC(4) + an undefined bit(8)
Perm(external)
ValueError: <flag 'Perm'> invalid value 13
    given 0b0 1101
  allowed 0b0 0111

Measured: enum.Flag defaults to boundary=STRICT (as of Python 3.11), so constructing it from any integer with bits outside the ones you defined raises ValueError immediately, naming exactly which bits were given versus allowed.

2. IntFlag accepts the identical integer with no error

import enum

class IntPerm(enum.IntFlag):
    READ = 1
    WRITE = 2
    EXEC = 4

external = 13
p = IntPerm(external)
print(p, int(p))
print(IntPerm.READ in p)
print(list(IntPerm))
13 13
True
[<IntPerm.READ: 1>, <IntPerm.WRITE: 2>, <IntPerm.EXEC: 4>]

Measured: enum.IntFlag defaults to boundary=KEEP, so the same invalid integer 13 constructs without error. p silently carries the undefined 8-bit alongside the two valid ones -- it prints as the bare int 13 instead of a readable flag combination, and list(IntPerm) still only shows the three defined members, giving no hint that an extra bit is set. Membership checks for defined bits (Perm.READ in p) still work correctly; there is just no signal that p also contains something you never defined.

3. The fix: pass boundary=enum.STRICT explicitly, or validate with Flag

import enum

class StrictIntPerm(enum.IntFlag, boundary=enum.STRICT):
    READ = 1
    WRITE = 2
    EXEC = 4

try:
    StrictIntPerm(13)
except ValueError as e:
    print("rejected:", e)
rejected: <flag 'StrictIntPerm'> invalid value 13
    given 0b0 1101
  allowed 0b0 0111

Measured: passing boundary=enum.STRICT to an IntFlag subclass restores the same rejection Flag gives by default. If you're parsing an external bitmask into an IntFlag specifically because you need int arithmetic or storage compatibility, add boundary=enum.STRICT (or enum.CONFORM to mask off the extra bits instead of raising) -- the plain default silently keeps whatever garbage bits arrive.

Frequently asked questions

Does enum.IntFlag raise an error for undefined bits like enum.Flag does?

No -- we measured enum.IntFlag(13) on a 3-bit flag (1, 2, 4) returning 13 with no error, silently keeping the undefined 4th bit, while the equivalent enum.Flag(13) raised ValueError.

Why do IntFlag and Flag behave differently for invalid values?

Because they have different default boundary settings as of Python 3.11: Flag defaults to boundary=STRICT (rejects undefined bits), IntFlag defaults to boundary=KEEP (keeps them silently) for backward compatibility with pre-3.11 int-like behavior.

How do I make IntFlag reject undefined bits like Flag does?

Pass boundary=enum.STRICT when defining the class, e.g. class Perm(enum.IntFlag, boundary=enum.STRICT): ... -- we confirmed this makes IntFlag raise ValueError on the same invalid integers that pass silently by default.

Is it safe to parse an external bitmask directly into an IntFlag?

Not with the default boundary -- we measured that IntFlag(external_int) accepts any integer, including ones with bits outside every defined member, with no indication anything was wrong. Use boundary=STRICT or boundary=CONFORM if the value comes from outside your program.

What does boundary=CONFORM do differently from the IntFlag default?

CONFORM masks off any undefined bits instead of keeping or rejecting them, per the enum module's design -- it's a middle option between KEEP (default, silent) and STRICT (raises) for handling unexpected bits.

Written with AI assistance. Measured on 2026-10-04 with Python 3.13.5's built-in enum module: constructed an IntFlag and a plain Flag with the same 3 defined bits (1, 2, 4), then passed each the integer 13 (which sets an undefined 4th bit alongside two valid ones) and compared the results, plus checked each class's ._boundary_ attribute. PlainHeaders is our extension; Python's enum module is built in.