Home Tutorials Standard Library

Standard Library

Python's enum Module: Enum, IntEnum, and Flag

Pyford Notes July 6, 2026 6 min read
Key points
  • An Enum defines a fixed, named set of values, catching typos at the point of use that a plain string constant would let through silently.
  • auto() assigns values automatically, useful when the members only need to be distinct, not tied to specific numbers.
  • IntEnum members behave like real integers, comparable and serialisable in contexts a plain Enum member isn't.
  • Flag members combine with the bitwise | operator, suiting sets of options like read/write/execute permissions.

The problem with plain strings as status values

A common early pattern for representing a fixed set of states is a plain string:

def process_order(status):
    if status == "peding":   # typo, but Python won't tell you
        hold_order()

The typo in "peding" is syntactically valid Python — it's just a string that will never equal "pending", so the branch silently never runs, with no error anywhere to point at the mistake. There's also nothing stopping any other string from being passed in where status is expected, since the type is just str.

Defining an Enum

enum.Enum defines a fixed set of named members that behave as their own distinct values, not interchangeable with arbitrary strings:

from enum import Enum

class OrderStatus(Enum):
    PENDING = "pending"
    SHIPPED = "shipped"
    DELIVERED = "delivered"

def process_order(status: OrderStatus):
    if status == OrderStatus.PENDING:
        hold_order()

process_order(OrderStatus.PENDING)   # correct
process_order(OrderStatus.PEDNING)   # AttributeError, caught immediately

Misspelling PENDING as PEDNING now raises an AttributeError the moment the code runs, instead of quietly comparing unequal forever. Each member also has a .name and a .value: OrderStatus.PENDING.name is the string "PENDING", while OrderStatus.PENDING.value is "pending", the value it was assigned in the class body.

Letting auto() assign the values

When the members only need to be distinct from each other, and the underlying value doesn't matter, auto() saves picking numbers or strings by hand:

from enum import Enum, auto

class Direction(Enum):
    NORTH = auto()
    SOUTH = auto()
    EAST = auto()
    WEST = auto()

print(Direction.NORTH.value)   # 1
print(Direction.EAST.value)    # 3

auto() assigns increasing integers starting from 1 by default. This is the right choice whenever code only ever compares members to each other (direction == Direction.NORTH) and never depends on the specific underlying number.

IntEnum: when you need comparisons or serialisation

A plain Enum member is not an integer, even if its value happens to be one — it doesn't support < or arithmetic against a plain int directly. IntEnum members behave as real integers everywhere an integer is expected, while still carrying their name:

from enum import IntEnum

class Priority(IntEnum):
    LOW = 1
    MEDIUM = 2
    HIGH = 3

print(Priority.HIGH > Priority.LOW)     # True
print(Priority.HIGH > 2)                # True, compares directly against a plain int
print(Priority.HIGH + 1)                # 4

This matters for sorting a list of tasks by priority, storing the value directly in a database column typed as an integer, or comparing against a threshold that arrives as a plain number from elsewhere in the system. The trade-off is that IntEnum gives up some of the type safety a plain Enum provides, since it will happily compare equal to an unrelated integer that was never meant to represent a priority at all.

Flag: combining options with bitwise operators

Some sets of options aren't mutually exclusive — a file might be readable, writable, both, or neither. Flag members are designed to combine with the bitwise | operator, similar in spirit to Unix file permission bits:

from enum import Flag, auto

class Permission(Flag):
    READ = auto()
    WRITE = auto()
    EXECUTE = auto()

perms = Permission.READ | Permission.WRITE
print(perms)                          # Permission.READ|WRITE
print(Permission.READ in perms)       # True
print(Permission.EXECUTE in perms)    # False

auto() inside a Flag class assigns powers of two (1, 2, 4, ...) rather than sequential integers, so each member occupies its own bit and combinations can be tested cleanly with in or unpacked with bitwise &. This is the standard-library equivalent of a set of boolean flags packed into one value, with named members instead of raw bit positions to remember.

Matching on enum members with match

Enum members work naturally as patterns in a match statement, which reads more clearly than a chain of if/elif comparisons once there are more than a couple of cases:

match status:
    case OrderStatus.PENDING:
        hold_order()
    case OrderStatus.SHIPPED:
        notify_customer()
    case OrderStatus.DELIVERED:
        close_order()

Because Enum guarantees a fixed, closed set of members, a match block over an enum is one of the few places in Python where it's reasonable to reason about exhaustiveness by eye — if every member has a case, and a new member is added to the enum later, that's the natural moment to also add a matching case here. The full set of enum types, including StrEnum and IntFlag, is covered in the official enum module documentation.