Someone types 'medum' into the size field and the price lookup returns nothing. A fixed set of names would have caught it at the door. Why is an Enum safer than a bare string?
An Enum gives a fixed set of named values a proper type. Members are unique objects, which makes comparisons and valid choices clearer than passing around bare strings. The name and value are both available when you need to display or serialize a member. You will also keep auto values internal, combine permissions, and validate stored values.
Define named members
Subclass Enum and assign names to values. Color.RED is a member object, not the string "red". Use .name for the identifier and .value for the underlying value. Members compare by identity.
from enum import Enum
class Color(Enum):
RED = "red"
GREEN = "green"
print(Color.RED.value)
print(Color.RED.name)Names with stable values
Iterate over the members
An Enum class is iterable in definition order. This is useful for building menus, validating choices, and generating documentation. Iteration yields members, not their raw values.
from enum import Enum
class Color(Enum):
RED = "red"
GREEN = "green"
for color in Color:
print(color.name, color.value)A small fixed set
Look up by value or name
Calling the enum with a value returns the matching member, or raises ValueError if there is none. Square brackets look up by member name and raise KeyError when the name is unknown. Choose the lookup that matches your input.
from enum import Enum
class Color(Enum):
RED = "red"
GREEN = "green"
print(Color("green"))
print(Color["RED"])Two kinds of lookup
auto() values are an implementation detail
auto() assigns consecutive integers, which is convenient inside the program but a poor storage format. The numbers can change when members are inserted. @unique catches accidental aliases, where two names receive the same value.
from enum import Enum, auto, unique
@unique
class Stage(Enum):
NEW = auto()
PAID = auto()
SHIPPED = auto()
print([(stage.name, stage.value) for stage in Stage])
print(Stage.PAID is Stage(2))Names are stable; auto numbers are local
Combine permissions with Flag
Flag members are bit values that can be combined with |. Test a permission with a bitwise AND and an equality check, not by comparing the whole combined value. Store the combined integer only if your boundary has a documented format.
from enum import Flag, auto
class Permission(Flag):
READ = auto()
WRITE = auto()
ADMIN = READ | WRITE
editor = Permission.READ | Permission.WRITE
print(editor)
print((editor & Permission.READ) == Permission.READ)
print((editor & Permission.ADMIN) == Permission.ADMIN)Bit flags for small permission sets
Store the value, validate on the way back
Enum members are not JSON values. Store member.value at the boundary, then call the enum class to validate and reconstruct the member. An unknown stored value raises ValueError, which is exactly the failure you want instead of silently accepting a typo.
from enum import Enum
class Plan(Enum):
FREE = "free"
PRO = "pro"
payload = {"plan": Plan.PRO.value}
restored = Plan(payload["plan"])
print(restored.name, restored.value)
try:
Plan("team")
except ValueError as error:
print(type(error).__name__, error)A value at rest, a member in code
Key points
- Enum members are unique objects with a name and a value.
- Use .name and .value to access the parts.
- Iteration yields members in definition order.
- Color(value) looks up by value; Color[name] looks up by name.
- Invalid lookups raise ValueError or KeyError depending on the method.
- Do not persist auto values; use Flag for combinations; validate stored enum values.
Common mistakes
- Comparing an enum member to its raw value and expecting equality.
- Using a bare string where a closed set of choices is required.
- Forgetting that lookup by value and lookup by name are different.
- Writing an Enum member directly into JSON and assuming it serializes as its value.
Define a Color enum with RED and GREEN, then print Color.RED.value and look up Color('green').
Try it yourself
Change the values, predict the output, then run the code. The Python runtime is downloaded the first time you press Run, so the first run can take a few seconds.
Practice this lesson
12 questions with immediate explanations.