Python Enum Types — Core Concepts
Why Enums exist
Magic numbers and magic strings scatter through codebases like weeds. if role == 3 or if status == "actve" (note the typo) — both compile fine, both create bugs that are hard to find. Enums give you a closed set of named constants that Python’s type system can validate.
Defining an Enum
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
Each member has a .name (the string "RED") and a .value (the integer 1). You access members with Color.RED or Color(1) or Color["RED"].
The Enum family
| Class | Values | Compares with | Use when |
|---|---|---|---|
Enum | Any type | Only other members of same Enum | Default choice — strictest |
IntEnum | int | Regular ints | Interop with C libraries, legacy APIs |
StrEnum (3.11+) | str | Regular strings | JSON APIs, database columns |
Flag | int (powers of 2) | Bitwise combinations | Permissions, feature flags |
IntFlag | int (powers of 2) | Regular ints + bitwise | Low-level protocol flags |
Enum vs IntEnum: the key trade-off
from enum import Enum, IntEnum
class StrictColor(Enum):
RED = 1
class LooseColor(IntEnum):
RED = 1
StrictColor.RED == 1 # → False (Enum doesn't equal raw int)
LooseColor.RED == 1 # → True (IntEnum does)
LooseColor.RED + 10 # → 11 (IntEnum participates in math)
Enum prevents accidental comparison with raw values — that’s a feature, not a bug. Use IntEnum only when you need backward compatibility with code that expects plain integers.
StrEnum for APIs
from enum import StrEnum
class OrderStatus(StrEnum):
PENDING = "pending"
SHIPPED = "shipped"
DELIVERED = "delivered"
# Works directly in JSON, SQL queries, template rendering
assert OrderStatus.PENDING == "pending"
Before Python 3.11, teams used class OrderStatus(str, Enum) — the same pattern, just more verbose.
Flag for combinable options
from enum import Flag, auto
class Permission(Flag):
READ = auto()
WRITE = auto()
EXECUTE = auto()
user_perms = Permission.READ | Permission.WRITE
print(Permission.READ in user_perms) # → True
print(Permission.EXECUTE in user_perms) # → False
Flags use bitwise OR to combine values. This mirrors Unix file permissions and hardware register flags.
Auto-generating values
auto() assigns incrementing integers (1, 2, 3…) or, for Flag, powers of two. You can override _generate_next_value_ for custom behavior:
from enum import Enum, auto
class Direction(Enum):
@staticmethod
def _generate_next_value_(name, start, count, last_values):
return name.lower()
NORTH = auto() # "north"
SOUTH = auto() # "south"
Common misconception
Many developers think Enum members are singletons by coincidence. They’re singletons by design. Color.RED is Color.RED is always True. You cannot create a second instance with the same value — Python returns the existing member. This makes Enums safe for identity comparison with is.
Iteration and membership
list(Color) # → [Color.RED, Color.GREEN, Color.BLUE]
len(Color) # → 3
Color.RED in Color # → True
Enums are iterable, have a length, and support in checks. This makes them useful for validation and generating dropdown options in UIs.
Practical checklist
- Default to
Enumfor maximum safety - Use
StrEnumwhen values will be serialized (JSON, DB, configs) - Use
IntEnumonly for C interop or legacy integer-based APIs - Use
Flagwhen values can be combined - Never compare
Enummembers with==against raw values — it will always beFalse
The one thing to remember: Start with the strict Enum base class and only relax to IntEnum or StrEnum when you have a concrete reason — strictness catches bugs, looseness invites them.
See Also
- Python Atexit How Python's atexit module lets your program clean up after itself right before it shuts down.
- Python Bisect Sorted Lists How Python's bisect module finds things in sorted lists the way you'd find a word in a dictionary — by jumping to the middle.
- Python Contextlib How Python's contextlib module makes the 'with' statement work for anything, not just files.
- Python Copy Module Why copying data in Python isn't as simple as it sounds, and how the copy module prevents sneaky bugs.
- Python Dataclass Field Metadata How Python dataclass fields can carry hidden notes — like sticky notes on a filing cabinet that tools read automatically.