Back to Blog
Python

Python IntFlag: Named Bitwise Flags

Use Python's IntFlag to replace magic-number bitmasks with named flags that still behave as integers. See how to define, combine, test, and extend them.

PythonenumIntFlagbitwise operationsflags
Illustration of Python IntFlag combining named bitwise flags into a single integer value.

When you need to represent a set of boolean options in Python, you can reach for an integer and assign each option a bit position. That works, but it leaves you with magic numbers scattered through the code. Python's enum.IntFlag gives you named constants that still behave like integers, so you get readable code without losing the ability to use them in bitwise operations. This article explains how IntFlag works, where it differs from Flag, and how to use it effectively in real code.

What IntFlag Provides Over Plain Integers

Using plain integers for flags means every check involves a bitwise operator and a numeric literal. For example, if permissions & 4: tells the reader nothing about what 4 represents. IntFlag solves this by letting you define a set of named members, each with a value that is a power of two. The resulting objects are subclasses of int, so they can be used anywhere an integer is expected, including arithmetic, comparisons, and serialization.

The key advantage is that you get both the readability of an enum and the compactness of a bitmask. Instead of writing permissions = 7 and later trying to remember which bits are set, you write permissions = Permissions.READ | Permissions.WRITE | Permissions.EXECUTE. The intent is explicit, and the value is still an integer that can be stored in a database or sent over a wire.

Defining an IntFlag Enum

To create an IntFlag, subclass enum.IntFlag and assign members with values that are powers of two. The enum module will automatically handle the bitwise operations for you.

from enum import IntFlag class Permissions(IntFlag): READ = 1 WRITE = 2 EXECUTE = 4

Each member is an instance of Permissions and also an int. For a named member to represent a single option, give it a distinct power of two. If two names share the same value, the later name becomes an alias; if a member value is a combination of other members, it is a pseudo-flag. auto() works for Flag and IntFlag: it returns the next power of two. For most cases, explicit values are clearer.

Combining and Testing Flags

Once you have an IntFlag class, you can combine members with the bitwise OR operator |, and test membership with the bitwise AND operator &. The result of a combination is also an IntFlag instance, not a plain integer.

perms = Permissions.READ | Permissions.WRITE print(perms) # Permissions.READ|WRITE if perms & Permissions.READ: print('can read') if perms & Permissions.EXECUTE: print('can execute') # not printed

To test a specific flag, Permissions.READ in perms is a shorthand for the & check on a single member. bool(perms) returns True if any bit is set, which is useful in conditional statements. Because IntFlag is an int, you can also use it in arithmetic, though that is rarely needed.

IntFlag vs Flag: When the Integer Behavior Matters

Python's enum module also provides Flag, which behaves like IntFlag but does not inherit from int. The choice between them depends on whether you need integer compatibility.

FeatureIntFlagFlag
Inherits from intYesNo
Can be used in integer contextsYesNo
Can be compared to plain integersYesNo
Serializes to integer automaticallyYesRequires .value
Type safetyGoodStronger (no accidental mixing with ints)

Use IntFlag when you need to pass flags to functions that expect integer bitmasks, such as os.open() or a C extension. Use Flag when you want to keep flag operations within the enum type and prevent accidental mixing with plain integers. Neither type rejects unknown bit combinations by default; if you need validation, define _missing_ or use a strict FlagBoundary on Python versions that support it.

Handling Unknown Flag Combinations

An IntFlag instance can hold any combination of bits, not just those that correspond to defined members. For example, Permissions(8) is valid even if no member has value 8. This is intentional and mirrors how bitmasks work. IntFlag iteration yields canonical members, so you can check which defined flags are present in a combined value.

combined = Permissions.READ | Permissions.WRITE for flag in Permissions: if flag in combined: print(f'{flag.name} is set')

If you need to handle unknown bits, you can override _missing_ to return a custom instance or None. Returning None makes Permissions(value) raise a ValueError, which is useful when you receive flags from an external system and want to reject unrecognized bits. Keep in mind that IntFlag values are not limited to powers of two; you can create a member with value 3, but that would overlap with READ and WRITE, making it ambiguous. Stick to powers of two for individual members.

Performance and Memory Considerations

IntFlag instances are Python objects, so they carry a small overhead compared to raw integers. For most applications, this overhead is negligible. The real cost appears when you perform many bitwise operations in a tight loop, because each operation creates a new IntFlag object. If you are working with millions of operations, you might see a measurable difference. In such cases, you can fall back to plain integers and convert to IntFlag only at the boundaries of your code.

Memory usage is also slightly higher than a plain integer, but again, this matters only when you store a large number of flags. The benefit of readability and type safety usually outweighs the cost. If you need to serialize flags to a compact format, you can use .value to get the integer and reconstruct the IntFlag later with Permissions(value).

Common Pitfalls and How to Avoid Them

The most frequent mistake is assigning non-power-of-two values to members. This causes unexpected behavior when combining flags, because overlapping bits make it impossible to distinguish individual members. Always use values like 1, 2, 4, 8, and so on.

Another pitfall is converting IntFlag values to raw integers too early. For example, storing .value, doing bitwise math on the plain int, and only reconstructing the IntFlag later can hide which flags are being manipulated. Keep values as IntFlag until you reach the API boundary that needs a plain integer, and reconstruct with Permissions(value) when reading them back.

Finally, be careful when using IntFlag in a boolean context. An empty combination, such as Permissions(0), evaluates to False, which is usually what you want. Any non-zero combination is truthy, so bool(perms) only tells you that at least one bit is set; use & or in to check a specific flag.

Extending IntFlag with Custom Methods

You can add methods to an IntFlag class to encapsulate domain logic. For example, you might want a method that returns a human-readable list of active flags, or one that validates whether a given combination is allowed.

class Permissions(IntFlag): READ = 1 WRITE = 2 EXECUTE = 4 def names(self): return [flag.name for flag in type(self) if flag in self] @classmethod def from_names(cls, names): result = cls(0) for name in names: result = result | getattr(cls, name) return result

These methods make the flag type self-documenting and reduce duplication across the codebase. Because IntFlag is an int, you can also use it directly in functions that expect integer flags, such as os.open(). For example, you could define an OpenFlags class that maps to the constants in the os module, giving you named flags for file open modes while still passing the integer value to the system call. This pattern is useful when you want to keep your code readable without sacrificing compatibility with lower-level APIs.

Python IntFlag: Define, Combine, and Test Named Bitwise Flags | RYUSLOG DEV