🐍 Python Course · Stage 1 · Lesson 18 of 89 · Enums
Course Overview →

🟩 Start with the Beginner tab. Read it fully before moving on.

← Dataclasses Abstract Base Classes →
thecodex.expert · The Codex Family of Knowledge
Python

Enumerations (enum) in Python

An enum is a set of named constant values bound to a single type — replacing scattered magic numbers and strings with readable, comparable, self-documenting members.

Python 3.13 docs.python.org Last verified:
Canonical Definition

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.

🟩 Beginner

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.

⏱ 25 min📄 2 sections🔶 Prerequisite: Object-Oriented Python
The idea

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.

Pythonwhy_enum.py
# 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  -> AttributeError
Enum members are singletons

Each 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

Pythonaccessing.py
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

Continue to Error Handling →

🔵 Intermediate

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.

⏱ 30 min📄 2 sections🔶 Prerequisite: Beginner tab

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.

Pythonenum_types.py
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)   # False
💡

Duplicate 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

Pythonmethods_unique.py
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 values
Commonly confused
Enum vs IntEnum. A plain Enum member is NOT equal to its value: Color.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.
Duplicate values create aliases, not new members. If two members share a value, the second becomes an alias of the first — it does not appear in iteration and 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)

Continue to Error Handling →

🔴 Expert

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.

⏱ 20 min📄 1 section🔶 Prerequisite: After Stage 2

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.

Pythonenum_internals.py
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

Continue to Error Handling →

Sources

1
Python Standard Library — enum. docs.python.org/3/library/enum.html.
2
PEP 435 — Adding an Enum type to the Python standard library. peps.python.org/pep-0435/.
3
Python Standard Library — enum.StrEnum (Python 3.11). docs.python.org/3/library/enum.html#enum.StrEnum.
4
Python HOWTO — Enum HOWTO. docs.python.org/3/howto/enum.html.
Source confidence: High Last verified: Primary source: Python enum module (PEP 435)