Enums
Enumerations replace magic strings and integers with named, typed constants. Members are singletons - identity-stable and self-documenting in APIs, configs, and match statements.
Recipe
from enum import StrEnum, auto
class Status(StrEnum):
PENDING = "pending"
ACTIVE = "active"
CLOSED = "closed"
class Priority(StrEnum):
LOW = auto()
MEDIUM = auto()
HIGH = auto()When to reach for this:
- HTTP methods, order states, role names
- Config values validated against fixed set
- Exhaustive
matchbranching - Bitmask permissions with
Flag
Working Example
from enum import Flag, StrEnum, auto
class Status(StrEnum):
PENDING = "pending"
ACTIVE = "active"
CLOSED = "closed"
class Role(StrEnum):
ADMIN = "admin"
DEV = "dev"
VIEWER = "viewer"
class Permission(Flag):
READ = auto()
WRITE = auto()
EXECUTE = auto()
ADMIN_PERMS = Permission.READ | Permission.WRITE | Permission.EXECUTE
def authorize(role: Role, required: Role) -> bool:
hierarchy = {Role.VIEWER: 0, Role.DEV: 1, Role.ADMIN: 2}
return hierarchy[role] >= hierarchy[required]
def can_read(perms: Permission) -> bool:
return Permission.READ in perms
def describe_status(status: Status) -> str:
match status:
case Status.PENDING:
return "waiting"
case Status.ACTIVE:
return "running"
case Status.CLOSED:
return "done"
if __name__ == "__main__":
print(authorize(Role.ADMIN, Role.DEV))
print(can_read(ADMIN_PERMS))
print(describe_status(Status.ACTIVE))What this demonstrates:
StrEnumserializes to string value for JSON logsFlagcombines with|and tests withinmatchon enum members for exhaustive handlingauto()generates values for non-string enums
Deep Dive
How It Works
- EnumMeta - Creates unique member objects at class definition.
- Value vs identity -
Status.ACTIVE is Status.ACTIVEalways True. - StrEnum - Subclass of
strandEnum- compares equal to its string value. - IntEnum - Subclass of
int- use cautiously when you need int behavior. - Functional API -
Enum('Color', ['RED', 'GREEN'])for dynamic enums.
Enum Varieties
| Class | Member type |
|---|---|
Enum | Generic |
StrEnum | str |
IntEnum | int |
Flag | bitmask int |
Python Notes
# JSON serialization
status = Status.ACTIVE
payload = {"status": status} # StrEnum -> "active" in many encoders
# get by value
Status("active") # returns Status.ACTIVEGotchas
- IntEnum isinstance int - Passes to functions expecting int but may confuse type checkers. Fix: Prefer StrEnum or plain Enum.
- Comparing to raw strings without StrEnum -
Status.ACTIVE == "active"False on plain Enum. Fix: StrEnum or compare.value. - Duplicate values alias - Second name aliases first member. Fix: Use
@enum.uniquedecorator. - Mutable enum members - Cannot assign new attrs to members. Fix: Attach data via
Enumwith tuple values(name, extra). - Flag infinite members - Combined flags create composite members. Fix: Document composite values or use explicit subsets.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
Literal["a","b"] | Type hints only | Need runtime validation |
| const module strings | Tiny scripts | API stability matters |
Pydantic Literal | HTTP validation | Internal domain enums |
| database lookup table | Dynamic user-defined values | Fixed system states |
FAQs
Enum vs Literal?
Enum runtime objects with identity. Literal only for static typing of fixed strings.
StrEnum JSON?
Often serializes as string value - verify with your JSON encoder (stdlib json uses value for StrEnum).
How get all members?
list(Status) or Status.__members__.values().
unique decorator?
@unique on Enum class raises if two names map to same value unintentionally.
Flag operators?
| union, & intersection, ~ invert within Flag definition.
enum in match?
Exhaustive matching - mypy/pyright warn on missing cases with strict settings.
extend Enum?
Subclassing Enum discouraged except special cases - compose instead.
auto() values?
Integers incrementing from one - override if stable values needed across versions.
Django choices?
TextChoices/IntegerChoices are Django enum wrappers - similar patterns.
database storage?
Store .value in column; reconstruct with Enum(value) on read.
Related
- Control Flow - match/case
- Type Hints Basics - Literal types
- Literal, Final & Annotated - precise constants
- dataclasses - enum fields in records
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.