An enumeration is a class derived from enum.Enum whose members are unique constant values. Defined in the enum module (PEP 435, Python 3.4+), each member has a name and a value, and members are singletons that compare by identity.
Named constants done right
What you’ll learn: Why enums replace magic numbers and strings, how to define an Enum, the three ways to access members (by attribute, value, name), and how to iterate over members.
How to read this tab: Replace a set of magic strings in your own code with an enum. The readability gain is immediate.
An enum gives names to a fixed set of related values. Instead of writing status = 1 and trying to remember what 1 means, you write status = Status.ACTIVE — readable, typo-proof, and self-documenting.
Enums turn silent typo bugs into immediate errors. With a magic string, status = "activ" (typo) fails silently somewhere later. With an enum, Status.ACTIV raises AttributeError instantly, at the point of the mistake.
Why enums exist
Without enums, code is full of "magic" numbers and strings whose meaning is unclear and whose typos are silent bugs. Enums replace them with named members.
# WITHOUT enum — magic values, error-prone
status = "active" # typo "activ" would be a silent bug
if status == "active": ...
# WITH enum — named, typo-proof, self-documenting
from enum import Enum
class Status(Enum):
ACTIVE = "active"
INACTIVE = "inactive"
PENDING = "pending"
BANNED = "banned"
status = Status.ACTIVE
print(status) # Status.ACTIVE
print(status.name) # "ACTIVE"
print(status.value) # "active"
if status == Status.ACTIVE:
print("User is active")
# A typo is now an AttributeError, caught immediately:
# Status.ACTIV -> AttributeErrorEach enum member exists exactly once. Status.ACTIVE is Status.ACTIVE is always True, and you compare members with is or ==. Two members can never accidentally be equal unless you deliberately alias them.
Three access styles, each for a different situation. By attribute (Color.RED) when you know the member at write time. By value (Color(1)) when converting from stored data. By name (Color["RED"]) when converting from a string, e.g. user input or config.
Defining and accessing enums
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
# Three ways to access a member
print(Color.RED) # Color.RED (by attribute)
print(Color(1)) # Color.RED (by value)
print(Color["RED"]) # Color.RED (by name)
# Members have .name and .value
print(Color.RED.name) # "RED"
print(Color.RED.value) # 1
# Iterate over all members (in definition order)
for color in Color:
print(color.name, color.value) # RED 1 / GREEN 2 / BLUE 3
# Membership and count
print(len(Color)) # 3
print(Color.RED in Color) # True✅ Beginner tab complete
- I can define an Enum and access members by attribute, value, or name
- I know member.name and member.value
- I know enum members are singletons compared with is or ==
- I can iterate over all members with a for loop
IntEnum, StrEnum, Flag, and auto()
What you’ll learn: auto() for automatic values, IntEnum and StrEnum for members that behave as int/str, Flag for bitwise combinations, enum methods, aliases, and @unique.
How to read this tab: Learn the difference between plain Enum and IntEnum — it determines whether members compare equal to raw numbers.
Plain Enum members are NOT equal to their values. Color.RED == 1 is False for a plain Enum but True for an IntEnum. Use IntEnum/StrEnum only when you genuinely need int/str behaviour — otherwise plain Enum is safer because it prevents accidental comparison with raw values.
IntEnum, StrEnum, Flag, and auto()
The enum module provides specialised base classes. auto() assigns values automatically. IntEnum and StrEnum make members behave as int/str. Flag supports bitwise combination.
from enum import Enum, IntEnum, Flag, auto
# auto() — assigns 1, 2, 3, ... automatically
class Direction(Enum):
NORTH = auto()
SOUTH = auto()
EAST = auto()
WEST = auto()
print(Direction.NORTH.value) # 1
# IntEnum — members ARE integers, comparable with int
class Priority(IntEnum):
LOW = 1
MEDIUM = 2
HIGH = 3
print(Priority.HIGH > Priority.LOW) # True — int comparison
print(Priority.HIGH == 3) # True — equals the int
print(sorted([Priority.HIGH, Priority.LOW])) # [LOW, HIGH]
# StrEnum (Python 3.11+) — members ARE strings
from enum import StrEnum
class Env(StrEnum):
DEV = "development"
PROD = "production"
print(Env.PROD == "production") # True
print(Env.PROD.upper()) # "PRODUCTION" — string methods work
# Flag — combine members with bitwise operators
class Permission(Flag):
READ = auto()
WRITE = auto()
EXECUTE = auto()
access = Permission.READ | Permission.WRITE
print(Permission.READ in access) # True
print(Permission.EXECUTE in access) # FalseDuplicate values create aliases, not distinct members. A second member with the same value becomes an alias of the first — excluded from iteration, and is returns True between them. Add @unique to forbid this when it is not intended.
Methods, aliases, and @unique
from enum import Enum, unique
# Enums can have methods like any class
class Planet(Enum):
MERCURY = (3.303e+23, 2.4397e6)
EARTH = (5.976e+24, 6.37814e6)
def __init__(self, mass, radius):
self.mass = mass
self.radius = radius
@property
def surface_gravity(self):
G = 6.67300e-11
return G * self.mass / (self.radius ** 2)
print(f"{Planet.EARTH.surface_gravity:.2f}") # 9.80
# Aliases — two names, same value (second is an alias)
class Status(Enum):
ACTIVE = 1
RUNNING = 1 # alias for ACTIVE
print(Status.RUNNING is Status.ACTIVE) # True
print(list(Status)) # [Status.ACTIVE] — aliases excluded from iteration
# @unique — forbid aliases, raise ValueError on duplicate values
@unique
class Weekday(Enum):
MON = 1
TUE = 2
# WED = 1 # would raise ValueError: duplicate valuesColor.RED == 1 is False. An IntEnum member IS: Priority.LOW == 1 is True. Use IntEnum only when you genuinely need integer behaviour (e.g. comparing or sorting); otherwise plain Enum is safer because it prevents accidental comparison with raw numbers.is returns True between them. Use @unique to forbid this if it is not intended.✅ Intermediate tab complete
- I can use auto() to assign member values automatically
- I know IntEnum members equal their int value but plain Enum members do not
- I can combine Flag members with bitwise | and test with in
- I know @unique forbids aliases (duplicate values)
EnumMeta, the functional API, and _missing_
What you’ll learn: How EnumMeta builds member singletons and reverse mappings, the functional API for creating enums dynamically, and the _missing_ hook for handling unknown values.
How to read this tab: Read when building domain types or when you need case-insensitive or fallback enum lookups.
EnumMeta, member creation, and the functional API
Enums are implemented through a metaclass, EnumMeta (also exposed as EnumType). When you define an Enum subclass, the metaclass intercepts class creation, collects the member definitions, converts each into a singleton member instance, and stores them in _member_map_ and _value2member_map_. This is why members are singletons and why Color(1) can look up a member by value — the metaclass built a reverse mapping.
from enum import Enum
# Functional API — create an enum without the class syntax
Animal = Enum("Animal", ["DOG", "CAT", "BIRD"])
print(list(Animal)) # [Animal.DOG, Animal.CAT, Animal.BIRD]
Animal2 = Enum("Animal2", {"DOG": 1, "CAT": 2}) # with explicit values
# Internal mappings built by EnumMeta
class Color(Enum):
RED = 1
GREEN = 2
print(Color._member_map_) # {'RED': Color.RED, 'GREEN': Color.GREEN}
print(Color._value2member_map_) # {1: Color.RED, 2: Color.GREEN}
# _missing_ — hook for handling unknown values
class Status(Enum):
ACTIVE = 1
INACTIVE = 2
@classmethod
def _missing_(cls, value):
# called when Status(value) finds no match
return cls.INACTIVE # default fallback instead of ValueError
print(Status(99)) # Status.INACTIVE — via _missing_
# __members__ includes aliases; iteration does not
class S(Enum):
A = 1
B = 1 # alias
print(list(S.__members__)) # ['A', 'B'] (includes alias)
print([m.name for m in S]) # ['A'] (excludes alias)The _missing_ classmethod is a powerful hook: it is invoked when a value lookup (Status(value)) finds no matching member, letting you implement default values, case-insensitive matching, or aliasing logic. Combined with the functional API — Enum("Name", names) — and mix-in types like IntEnum, the enum module supports everything from simple constants to rich domain types with behaviour.
✅ Expert tab complete
- I know enums are built by the EnumMeta metaclass at class creation
- I can create an enum dynamically with the functional API: Enum("Name", [...])
- I can implement _missing_ to handle unknown values gracefully