enum

The enum module gives named, symbolic constants a real type -- Enum, IntEnum, and Flag replace loose collections of magic numbers or strings with a small, safe, self-documenting set of named values.

Many programs need a fixed, named set of related constants -- days of the week, HTTP methods, permission bits. Representing these as plain integers or strings works but loses type safety and self-documentation: nothing stops you from passing an unrelated int where a status code was expected. The enum module (standard library, since Python 3.4) solves this with a real class-based enumeration type.

What Is an Enum

An enumeration is a set of symbolic names bound to fixed, unique values. Python's Enum class (from the enum module) turns each such name into a distinct member object -- an instance of the enum class itself -- rather than a plain int or string, giving you type checking, readable reprs, and iteration for free.

Color.RED is not the integer 1 -- it's a distinct Color member. This is exactly the type safety a plain constant (RED = 1) does not give you.

from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

print(Color.RED)          # Color.RED
print(Color.RED.name)     # 'RED'
print(Color.RED.value)    # 1
print(type(Color.RED))    # <enum 'Color'>

Defining and Accessing Members

Enum members are looked up three ways: by attribute (Color.RED), by value (Color(1)), or by name (Color['RED']). All three return the exact same object.

Color(4) raises ValueError -- there is no member with that value. This validation is itself a benefit over a plain int, which accepts any value silently.

print(Color(1))          # Color.RED -- lookup by value
print(Color['RED'])      # Color.RED -- lookup by name
print(Color.RED is Color(1))  # True -- same singleton object

auto(): Automatic Values

When the specific integer value of each member doesn't matter -- only that each is distinct -- enum.auto() assigns increasing integers automatically, starting at 1, so you don't have to number members by hand (and risk a typo creating a duplicate).

auto()'s exact numbering (starting at 1, incrementing by 1) is standard Enum behavior, but the numbers themselves are an implementation detail you generally shouldn't rely on for meaning -- use the member names, not their values, in your logic.

from enum import Enum, auto

class Status(Enum):
    PENDING = auto()
    ACTIVE = auto()
    DONE = auto()

print([m.value for m in Status])   # [1, 2, 3]

IntEnum: Enums That Behave Like Integers

A plain Enum member does not compare equal to a plain int, even if the values match (Color.RED == 1 is False). IntEnum members are also int subclasses, so they compare equal to, and interoperate arithmetically with, plain integers -- useful when you need enum readability but must still interface with code (or a wire protocol) expecting raw ints.

Prefer plain Enum when you don't need int interoperability -- it's stricter and catches accidental comparisons to unrelated numbers that IntEnum would silently allow.

from enum import IntEnum

class HttpStatus(IntEnum):
    OK = 200
    NOT_FOUND = 404

print(HttpStatus.OK == 200)     # True -- IntEnum compares equal to plain int
print(HttpStatus.OK + 1)         # 201 -- arithmetic works, like a real int
print(isinstance(HttpStatus.OK, int))  # True

Flag and IntFlag: Bitwise-Combinable Enums

Flag (and its int-compatible sibling IntFlag) is for enums representing combinable options, like file permissions. Members are combined with the bitwise | operator, and each combination is itself a valid Flag value; membership is tested with in or the & operator.

auto() under Flag assigns powers of two (1, 2, 4, ...) automatically, so combinations never collide -- this is different from auto()'s plain sequential numbering under a regular Enum.

from enum import Flag, auto

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

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

Iterating and Checking Membership

Enum classes are iterable in definition order, and support the in operator to check whether a value belongs to the enumeration.

for status in Status:
    print(status.name, status.value)

print(Status.ACTIVE in Status)   # True

Adding Methods to an Enum

Because Enum is a real class, you can define ordinary methods on it, giving each member behavior alongside its name and value.

class Direction(Enum):
    NORTH = (0, 1)
    SOUTH = (0, -1)
    EAST = (1, 0)
    WEST = (-1, 0)

    def opposite(self) -> 'Direction':
        dx, dy = self.value
        return Direction((-dx, -dy))

print(Direction.NORTH.opposite())   # Direction.SOUTH

Identity and Equality Considerations

Enum members are singletons: for a given enum class, Color.RED always refers to the exact same object, so is and == give identical results for member-to-member comparisons -- Color.RED is Color(1) is True. This makes is idiomatic (and common) for comparing plain Enum members, unlike the general rule of preferring == for value comparisons on other types. The exception is IntEnum/IntFlag, whose members also compare == to plain ints (but are still not the same object as those ints, so is against an int is still wrong).

print(Color.RED is Color.RED)         # True -- singleton
print(Color.RED == Color.RED)          # True
print(Color.RED is Color(1))           # True -- same object, looked up two ways
print(HttpStatus.OK is 200)             # False -- IntEnum member is not the same object as the int

Common Mistakes

The most frequent mistakes are comparing a plain Enum member directly to a raw value (Color.RED == 1 is False, unlike IntEnum), and reusing IntEnum everywhere by default when a plain Enum's stricter comparisons would catch more bugs.

Complete Example: A State Machine with Enum

Because enum members are hashable and singleton, they work naturally as dict keys and set elements -- exactly what a state-transition table needs.

from enum import Enum, auto

class OrderState(Enum):
    PLACED = auto()
    SHIPPED = auto()
    DELIVERED = auto()
    CANCELLED = auto()

VALID_TRANSITIONS = {
    OrderState.PLACED: {OrderState.SHIPPED, OrderState.CANCELLED},
    OrderState.SHIPPED: {OrderState.DELIVERED},
}

def can_transition(current: OrderState, target: OrderState) -> bool:
    return target in VALID_TRANSITIONS.get(current, set())

print(can_transition(OrderState.PLACED, OrderState.SHIPPED))     # True
print(can_transition(OrderState.DELIVERED, OrderState.PLACED))   # False

Quick Reference

Enum       plain named constants, strict comparisons (no == to raw values)
IntEnum    Enum + int subclass -- compares equal to and does arithmetic with plain ints
Flag       combinable options via | , tests membership via 'in'
IntFlag    Flag + int subclass
auto()     auto-assigns values (sequential ints for Enum, powers of two for Flag)

Key Takeaways

Enum members are distinct singleton objects, not the raw values they wrap -- Color.RED != 1 for a plain Enum.

IntEnum/IntFlag members are also real ints, so they compare equal to and do arithmetic with plain integers -- use them only when that interoperability is genuinely needed.

Flag/IntFlag support bitwise combination with | and membership testing with 'in', with auto() assigning powers of two.

Because members are singletons, 'is' is idiomatic for comparing plain Enum members, unlike most other Python types.

auto() assigns values automatically so you don't hand-number members and risk accidental duplicates.

Enum classes are real classes -- they can define methods, and their members are iterable, hashable, and usable as dict keys.

class Color(Enum): RED = 1 if Color.RED == 1: # False -- plain Enum doesn't equal a raw int ...

if Color.RED == Color(1): ... # or use IntEnum specifically when int-comparison is actually needed

A plain Enum member is never equal to the raw value it wraps; only IntEnum/IntFlag members compare equal to plain ints, and only because they are genuinely int subclasses.

class Priority(Enum): LOW = 1 MEDIUM = 1 # accidental duplicate value -- becomes an alias for LOW, not an error class Priority(Enum): LOW = 1 MEDIUM = 2 # each member should get a distinct value, or use auto()

Enum allows two names to share the same value, but the second becomes an alias for the first rather than a distinct member -- Priority.MEDIUM is Priority.LOW would be True, which is rarely intended. auto() avoids this by construction.

class Permission(Enum): # plain Enum -- combining members doesn't behave as a set of flags READ = auto() WRITE = auto() combined = Permission.READ | Permission.WRITE # TypeError class Permission(Flag): READ = auto() WRITE = auto() combined = Permission.READ | Permission.WRITE # works -- Flag supports bitwise combination

Only Flag and IntFlag support the | combination operator; a plain Enum raises TypeError because | isn't defined for its members.

Why does Color.RED == 1 evaluate to False for a plain Enum, even though RED = 1 in the class body?

Enum members are distinct objects of the enum class, not the raw values assigned to them -- the assigned value is stored as .value, but the member itself only compares equal to other members of the same enum (or, for IntEnum specifically, also to plain ints, because it's a genuine int subclass).

What does IntEnum add on top of a plain Enum, and when would you choose it?

IntEnum members are also int subclasses, so they compare equal to and support arithmetic with plain integers -- choose it when you must interoperate with code or protocols expecting raw ints (e.g. HTTP status codes), and prefer plain Enum otherwise for stricter, safer comparisons.

How does auto()'s numbering differ between a plain Enum and a Flag/IntFlag?

Under a plain Enum, auto() assigns sequential integers starting at 1 (1, 2, 3, ...); under Flag/IntFlag, auto() assigns powers of two (1, 2, 4, 8, ...) so that any bitwise combination of members produces a unique, unambiguous value.

Why is 'is' considered idiomatic for comparing plain Enum members, when it's generally discouraged for comparing other objects?

Enum members are singletons -- there is exactly one Color.RED object for the whole program, so Color.RED is Color(1) is always True for equal members, making 'is' both correct and slightly faster; this doesn't generalize to most other types, where two equal but distinct objects are common.

What happens if two Enum members are assigned the same value, and how does auto() prevent this by construction?

The second name becomes an alias for the first member rather than a separate distinct member (e.g. Priority.MEDIUM is Priority.LOW would be True), which is rarely intended; auto() sidesteps this because it assigns each successive member its own unique value automatically.

Why does a plain Enum raise TypeError on Permission.READ | Permission.WRITE, while Flag does not?

Bitwise combination (|) is only implemented for Flag and IntFlag members, whose whole purpose is representing combinable option sets; a plain Enum's members don't define that operator because they represent one exclusive choice among several, not a set of independently-combinable options.

Write an Enum called Weekday with members MONDAY through SUNDAY using auto(), and a function is_weekend(day) that returns True for SATURDAY or SUNDAY.

from enum import Enum, auto class Weekday(Enum): MONDAY = auto() TUESDAY = auto() WEDNESDAY = auto() THURSDAY = auto() FRIDAY = auto() SATURDAY = auto() SUNDAY = auto() def is_weekend(day: Weekday) -> bool: return day in (Weekday.SATURDAY, Weekday.SUNDAY) print(is_weekend(Weekday.SATURDAY)) # True print(is_weekend(Weekday.TUESDAY)) # False auto() assigns each Weekday member a distinct sequential value automatically; is_weekend checks membership in a tuple of the two weekend members using Enum's singleton-safe equality.

What does this print?

False True -- order combines CHEESE and OLIVES via bitwise OR; PEPPERONI was never combined in, so 'in' reports False for it and True for CHEESE, which is one of the combined flags.

Write an IntEnum called Priority (LOW=1, MEDIUM=2, HIGH=3) and a function that returns True if a given priority is at least MEDIUM, using integer comparison.

from enum import IntEnum class Priority(IntEnum): LOW = 1 MEDIUM = 2 HIGH = 3 def is_at_least_medium(p: Priority) -> bool: return p >= Priority.MEDIUM print(is_at_least_medium(Priority.HIGH)) # True print(is_at_least_medium(Priority.LOW)) # False

Because Priority is an IntEnum, its members are real ints and support ordering comparisons (>=) directly against each other, which a plain Enum does not support without adding order=True-style comparison methods manually.

from enum import Flag, auto

class Toppings(Flag):
    CHEESE = auto()
    PEPPERONI = auto()
    OLIVES = auto()

order = Toppings.CHEESE | Toppings.OLIVES
print(Toppings.PEPPERONI in order, Toppings.CHEESE in order)