The enum module provides type-safe enumerations. No more magic strings or mysterious integers scattered through your code.

Basic Enum

from enum import Enum
 
class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3
 
# Usage
color = Color.RED
print(color)        # Color.RED
print(color.name)   # 'RED'
print(color.value)  # 1
 
# Comparison
if color == Color.RED:
    print("It's red")
 
# Iteration
for c in Color:
    print(c)

auto() for Automatic Values

from enum import Enum, auto
 
class Status(Enum):
    PENDING = auto()   # 1
    RUNNING = auto()   # 2
    COMPLETE = auto()  # 3
    FAILED = auto()    # 4

Use when you don't care about specific values.

String Enums

from enum import Enum
 
class HttpMethod(Enum):
    GET = "GET"
    POST = "POST"
    PUT = "PUT"
    DELETE = "DELETE"
 
# Lookup by value
method = HttpMethod("GET")
print(method)  # HttpMethod.GET
 
# Use in API calls
requests.request(HttpMethod.POST.value, url)

Python 3.11+ has StrEnum:

from enum import StrEnum
 
class HttpMethod(StrEnum):
    GET = "GET"
    POST = "POST"
 
# Automatically converts to string
print(f"Method: {HttpMethod.GET}")  # "Method: GET"

IntEnum: Integer-Compatible

from enum import IntEnum
 
class Priority(IntEnum):
    LOW = 1
    MEDIUM = 2
    HIGH = 3
 
# Works like an integer
print(Priority.HIGH > Priority.LOW)  # True
print(Priority.MEDIUM + 1)           # 3

Caution: IntEnum compares equal to plain integers:

Priority.LOW == 1  # True (sometimes surprising)

Regular Enum doesn't:

Color.RED == 1  # False

Flag: Combinable Enums

from enum import Flag, auto
 
class Permission(Flag):
    READ = auto()
    WRITE = auto()
    EXECUTE = auto()
 
# Combine with |
user_perms = Permission.READ | Permission.WRITE
 
# Check with &
if Permission.READ in user_perms:
    print("Can read")
 
# Check multiple
if user_perms & (Permission.READ | Permission.WRITE):
    print("Can read or write")

Enum Methods

Add methods to enums:

from enum import Enum
 
class Planet(Enum):
    MERCURY = (3.303e+23, 2.4397e6)
    EARTH = (5.976e+24, 6.37814e6)
    MARS = (6.421e+23, 3.3972e6)
    
    def __init__(self, mass, radius):
        self.mass = mass
        self.radius = radius
    
    @property
    def surface_gravity(self):
        G = 6.67430e-11
        return G * self.mass / (self.radius ** 2)
 
print(Planet.EARTH.surface_gravity)  # 9.8...

Lookup Patterns

from enum import Enum
 
class Status(Enum):
    ACTIVE = "active"
    INACTIVE = "inactive"
    PENDING = "pending"
 
# By name
Status["ACTIVE"]  # Status.ACTIVE
 
# By value
Status("active")  # Status.ACTIVE
 
# Safe lookup
def get_status(value: str) -> Status | None:
    try:
        return Status(value)
    except ValueError:
        return None

Ensuring Unique Values

from enum import Enum, unique
 
@unique
class ErrorCode(Enum):
    NOT_FOUND = 404
    SERVER_ERROR = 500
    # DUPLICATE = 404  # Would raise ValueError

Aliases

Without @unique, duplicates create aliases:

from enum import Enum
 
class Status(Enum):
    RUNNING = 1
    STARTED = 1  # Alias for RUNNING
 
print(Status.STARTED is Status.RUNNING)  # True
print(list(Status))  # [Status.RUNNING] - only canonical

Practical Examples

State Machine

from enum import Enum, auto
 
class OrderState(Enum):
    CREATED = auto()
    PAID = auto()
    SHIPPED = auto()
    DELIVERED = auto()
    CANCELLED = auto()
    
    def can_transition_to(self, new_state):
        valid = {
            OrderState.CREATED: {OrderState.PAID, OrderState.CANCELLED},
            OrderState.PAID: {OrderState.SHIPPED, OrderState.CANCELLED},
            OrderState.SHIPPED: {OrderState.DELIVERED},
            OrderState.DELIVERED: set(),
            OrderState.CANCELLED: set(),
        }
        return new_state in valid[self]
 
order_state = OrderState.CREATED
if order_state.can_transition_to(OrderState.PAID):
    order_state = OrderState.PAID

Configuration Options

from enum import Enum
 
class LogLevel(Enum):
    DEBUG = 10
    INFO = 20
    WARNING = 30
    ERROR = 40
    CRITICAL = 50
    
    def should_log(self, message_level: 'LogLevel') -> bool:
        return message_level.value >= self.value
 
config_level = LogLevel.WARNING
if config_level.should_log(LogLevel.ERROR):
    print("Logging error")

Database Values

from enum import Enum
 
class UserRole(Enum):
    ADMIN = "admin"
    EDITOR = "editor"
    VIEWER = "viewer"
 
# SQLAlchemy column
# role = Column(SQLEnum(UserRole))
 
# Pydantic model
# class User(BaseModel):
#     role: UserRole

Quick Reference

from enum import Enum, IntEnum, Flag, StrEnum, auto, unique
 
# Basic enum
class Color(Enum):
    RED = 1
 
# Auto values
class Status(Enum):
    PENDING = auto()
 
# Integer-compatible
class Priority(IntEnum):
    LOW = 1
 
# Combinable flags
class Perm(Flag):
    READ = auto()
 
# String enum (3.11+)
class Method(StrEnum):
    GET = "GET"
 
# Ensure unique values
@unique
class Code(Enum):
    OK = 200
TypeUse Case
EnumGeneral purpose, type-safe
IntEnumNeed integer comparison
StrEnumNeed string conversion
FlagCombinable bit flags

Enums make your code self-documenting. Use them instead of magic strings and numbers.

React to this post: