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 objectauto(): 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)) # TrueFlag 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) # FalseIterating 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) # TrueAdding 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.SOUTHIdentity 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 intCommon 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)) # FalseQuick 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)