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

ClassValuesCompares withUse when
EnumAny typeOnly other members of same EnumDefault choice — strictest
IntEnumintRegular intsInterop with C libraries, legacy APIs
StrEnum (3.11+)strRegular stringsJSON APIs, database columns
Flagint (powers of 2)Bitwise combinationsPermissions, feature flags
IntFlagint (powers of 2)Regular ints + bitwiseLow-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 Enum for maximum safety
  • Use StrEnum when values will be serialized (JSON, DB, configs)
  • Use IntEnum only for C interop or legacy integer-based APIs
  • Use Flag when values can be combined
  • Never compare Enum members with == against raw values — it will always be False

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.

pythonstandard-librarytypes

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.