Back to Blog
Python

Python Flag Enum: Using Flag and IntFlag

python flag enum: Learn how to use Python's Flag and IntFlag enums to define bitwise flags, combine them, and test membership cleanly.

PythonenumflagsbitwiseIntFlag
Illustration of Python flag enum with bitwise OR operation combining two flags into a combined flag.

python flag enum requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When you need to represent a set of boolean options in Python, a flag enum is often cleaner than a list of strings or a dict of booleans. The standard library's enum module provides Flag and IntFlag specifically for this purpose. This article covers how to define, combine, and test flags using Python's flag enum types.

What Flag and IntFlag Provide

Flag and IntFlag are subclasses of Enum that implement the bitwise operators (|, &, ^, ~) and membership tests. They let you define named constants that can be combined into composite values, which is exactly the pattern used in many APIs for permissions, feature toggles, or protocol options.

The key difference between the two is that IntFlag members are also instances of int. This means an IntFlag can be passed to code that expects a plain integer, such as a function that takes a bitmask. Flag members are not integers; they are instances of the enum itself. That distinction affects how they interact with external code and with operations like serialization.

Both types support the auto() helper, which assigns powers of two automatically. This removes the need to manually write 1, 2, 4, 8, and so on, which is error-prone and hard to read.

Defining a Flag Enum

Here is a minimal Flag definition for file permissions:

from enum import Flag, auto class Permission(Flag): READ = auto() WRITE = auto() EXECUTE = auto()

Each member gets a value that is the next power of two. READ becomes 1, WRITE becomes 2, EXECUTE becomes 4. You can combine them with the bitwise OR operator:

read_write = Permission.READ | Permission.WRITE

The resulting value is a Permission instance with the combined bits set. You can test membership with the in operator:

if Permission.READ in read_write: print("read is allowed")

This works because Flag implements __contains__ to check whether all bits of the right operand are present in the left operand. It is equivalent to (read_write & Permission.READ) == Permission.READ.

If you need to pass the flag to a function that expects an integer, use IntFlag instead:

from enum import IntFlag, auto class Permission(IntFlag): READ = auto() WRITE = auto() EXECUTE = auto()

Now Permission.READ is an int subclass, so it can be passed directly to a function that accepts a bitmask. This is useful when you are interacting with a C library or an API that uses integer flags.

Combining and Testing Flags

The bitwise operators work as expected on both Flag and IntFlag. You can OR two flags together, AND them to test overlap, XOR to toggle, and invert with ~. The ~ operator returns the complement within the defined flag space, not the full integer complement. This is a common source of confusion.

For example:

combined = Permission.READ | Permission.WRITE print(combined) # Permission.READ|WRITE print(Permission.READ & combined) # Permission.READ print(Permission.EXECUTE & combined) # Permission 0x0

The zero value is a special member that represents no flags set. It is automatically available as Permission(0) and is falsy in boolean contexts.

You can also iterate over the individual flags in a combined value using the __iter__ method that Flag provides:

for flag in combined: print(flag)

This yields each set flag as a separate enum member, which is handy for logging or displaying a human-readable list.

Choosing Between Flag and IntFlag

The decision between Flag and IntFlag comes down to whether you need integer compatibility. Use Flag when you want strict enum semantics and do not want the members to be used as integers accidentally. This is safer because it prevents mixing flag values with unrelated integers in arithmetic operations.

Use IntFlag when you must pass the value to an external API that expects an integer bitmask, or when you need to store the value in a database column that is typed as an integer. IntFlag also allows you to use the value in a switch statement or as a dictionary key where the key type is int.

There is a subtle difference in how ~ behaves. For Flag, ~ produces a value that has all bits set that are not in the original, but only within the defined members. For IntFlag, ~ produces the integer complement, which may include bits outside the defined flag set. This can lead to unexpected results if you are not careful.

For example:

class Perm(IntFlag): READ = auto() WRITE = auto() print(~Perm.READ) # -2 (integer complement)

The negative value is rarely what you want. If you need to invert within the flag space, you should mask it with the combined value of all defined flags.

Handling Unknown Flag Combinations

When you receive a flag value from an external source, it may contain bits that do not correspond to any defined member. By default, Flag and IntFlag allow this; the value is still a valid instance, but it may not have a name. You can access it as Permission(7) and it will be a Permission instance with the combined bits.

If you want to reject unknown combinations, you can override _missing_ in your enum class. This method is called when the constructor receives a value that does not match any member. For example:

class Permission(Flag): READ = auto() WRITE = auto() EXECUTE = auto() @classmethod def _missing_(cls, value): raise ValueError(f"{value!r} is not a valid Permission combination")

Now Permission(7) raises a ValueError because 7 includes a bit that is not defined. This is useful when you want strict validation of incoming data.

Note that _missing_ is also called for the zero value. If you want to allow the empty flag, you need to handle that case explicitly.

Performance and Maintainability Considerations

Flag enums are implemented as Python classes, so there is a small overhead when creating a member or performing a bitwise operation. In practice, this overhead is negligible compared to the cost of I/O or network operations. The real benefit is maintainability: named flags are self-documenting and reduce the risk of using the wrong bitmask.

Using raw integers for flags is faster in microbenchmarks, but the difference is rarely significant in real applications. The bigger cost is the cognitive load of remembering which bit corresponds to which permission. A flag enum makes the code easier to read and less error-prone.

When you need to serialize a flag value, you can use the integer value directly. For Flag, you need to cast to int; for IntFlag, the value is already an int. This makes it easy to store in a database or send over JSON. When deserializing, you can pass the integer back to the enum constructor to get the corresponding flag instance.

One performance consideration is that iterating over a combined flag with for flag in combined uses the internal __iter__ method, which is implemented in Python. If you have a very large number of flags (dozens), the iteration may be slower than a manual bitmask loop, but this is rarely a bottleneck.

Compatibility and Version Notes

Flag and IntFlag were introduced in Python 3.6. If you are supporting older Python versions, you cannot use them directly. In that case, you can fall back to a plain Enum with manually assigned powers of two, but you lose the built-in bitwise operators and membership tests.

The auto() helper was also added in Python 3.6. In earlier versions, you must assign values explicitly. If you are on Python 3.6 or later, you can rely on auto() to keep the definitions concise.

Another version-related detail is that the _missing_ hook is called for any value that does not match a member, including the zero value. This behavior has been consistent since Python 3.6. If you are using a very recent Python version (3.11+), there are no changes to the flag enum API that affect the patterns described here.

When you use IntFlag, be aware that the integer complement ~ may produce negative numbers. This is a consequence of Python's integer semantics and is not specific to the enum implementation. If you need to invert within the flag space, you should mask the result with the combined value of all defined flags, as shown earlier.

python flag enum: Practical Usage and Code Examples | RYUSLOG DEV