Enums replace magic strings and integers with type-safe, self-documenting constants. Essential for configuration, state machines, and API design.

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
color == Color.RED   # True
color == 1           # False (use .value for int comparison)

String Enums

from enum import Enum
 
class Status(str, Enum):
    PENDING = "pending"
    ACTIVE = "active"
    COMPLETED = "completed"
 
# Works as string
status = Status.ACTIVE
print(f"Status: {status}")  # Status: active
print(status == "active")    # True (because str mixin)
 
# JSON serializable
import json
json.dumps({"status": status})  # '{"status": "active"}'

Auto Values

from enum import Enum, auto
 
class Priority(Enum):
    LOW = auto()      # 1
    MEDIUM = auto()   # 2
    HIGH = auto()     # 3
    CRITICAL = auto() # 4
 
# Custom auto
class HttpMethod(Enum):
    def _generate_next_value_(name, start, count, last_values):
        return name.upper()
    
    GET = auto()     # 'GET'
    POST = auto()    # 'POST'
    PUT = auto()     # 'PUT'
    DELETE = auto()  # 'DELETE'

IntEnum for Integer Operations

from enum import IntEnum
 
class Permission(IntEnum):
    READ = 4
    WRITE = 2
    EXECUTE = 1
 
# Works with integers
perm = Permission.READ
print(perm + Permission.WRITE)  # 6
print(perm > Permission.WRITE)  # True
print(perm == 4)                # True

Flag Enum for Bitwise Operations

from enum import Flag, auto
 
class Permissions(Flag):
    NONE = 0
    READ = auto()     # 1
    WRITE = auto()    # 2
    EXECUTE = auto()  # 4
    
    # Combinations
    READ_WRITE = READ | WRITE
    ALL = READ | WRITE | EXECUTE
 
# Combine flags
perms = Permissions.READ | Permissions.WRITE
print(perms)  # Permissions.READ|WRITE
 
# Check flags
Permissions.READ in perms  # True
Permissions.EXECUTE in perms  # False
 
# Iterate combined flags
for perm in perms:
    print(perm)  # Permissions.READ, Permissions.WRITE

Enum Methods

from enum import Enum
 
class HttpStatus(Enum):
    OK = 200
    NOT_FOUND = 404
    SERVER_ERROR = 500
    
    def is_success(self) -> bool:
        return 200 <= self.value < 300
    
    def is_error(self) -> bool:
        return self.value >= 400
    
    @classmethod
    def from_code(cls, code: int) -> 'HttpStatus':
        for status in cls:
            if status.value == code:
                return status
        raise ValueError(f"Unknown status code: {code}")
 
status = HttpStatus.OK
print(status.is_success())  # True
 
status = HttpStatus.from_code(404)
print(status)  # HttpStatus.NOT_FOUND

Enum with Properties

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

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: 'OrderState') -> bool:
        transitions = {
            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 transitions[self]
 
class Order:
    def __init__(self):
        self.state = OrderState.CREATED
    
    def transition(self, new_state: OrderState):
        if not self.state.can_transition_to(new_state):
            raise ValueError(f"Cannot transition from {self.state} to {new_state}")
        self.state = new_state
 
order = Order()
order.transition(OrderState.PAID)
order.transition(OrderState.SHIPPED)
# order.transition(OrderState.CREATED)  # ValueError!

Lookup by Name or Value

from enum import Enum
 
class Direction(Enum):
    NORTH = "N"
    SOUTH = "S"
    EAST = "E"
    WEST = "W"
 
# By name
Direction["NORTH"]     # Direction.NORTH
Direction["INVALID"]   # KeyError
 
# By value
Direction("N")         # Direction.NORTH
Direction("X")         # ValueError
 
# Safe lookup
def get_direction(value: str) -> Direction | None:
    try:
        return Direction(value)
    except ValueError:
        return None

Iteration and Membership

from enum import Enum
 
class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3
 
# Iterate all members
for color in Color:
    print(f"{color.name}: {color.value}")
 
# Check membership
Color.RED in Color  # True
 
# Get all values
list(Color)  # [Color.RED, Color.GREEN, Color.BLUE]
 
# Length
len(Color)  # 3

Aliases

from enum import Enum
 
class Color(Enum):
    RED = 1
    CRIMSON = 1  # Alias for RED
    GREEN = 2
    BLUE = 3
 
Color.RED is Color.CRIMSON  # True
 
# Only canonical members in iteration
list(Color)  # [Color.RED, Color.GREEN, Color.BLUE]
 
# Access all including aliases
Color.__members__
# {'RED': Color.RED, 'CRIMSON': Color.RED, 'GREEN': Color.GREEN, 'BLUE': Color.BLUE}

Unique Decorator

from enum import Enum, unique
 
@unique
class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3
    # CRIMSON = 1  # Would raise ValueError!

Configuration Enum

from enum import Enum
 
class Environment(str, Enum):
    DEVELOPMENT = "development"
    STAGING = "staging"
    PRODUCTION = "production"
    
    @property
    def debug(self) -> bool:
        return self == Environment.DEVELOPMENT
    
    @property
    def database_url(self) -> str:
        urls = {
            Environment.DEVELOPMENT: "sqlite:///dev.db",
            Environment.STAGING: "postgres://staging-db/app",
            Environment.PRODUCTION: "postgres://prod-db/app",
        }
        return urls[self]
 
env = Environment.DEVELOPMENT
print(env.debug)        # True
print(env.database_url) # sqlite:///dev.db

API Response Types

from enum import Enum
from typing import TypedDict
 
class ErrorCode(str, Enum):
    NOT_FOUND = "NOT_FOUND"
    UNAUTHORIZED = "UNAUTHORIZED"
    VALIDATION_ERROR = "VALIDATION_ERROR"
    INTERNAL_ERROR = "INTERNAL_ERROR"
    
    @property
    def http_status(self) -> int:
        mapping = {
            ErrorCode.NOT_FOUND: 404,
            ErrorCode.UNAUTHORIZED: 401,
            ErrorCode.VALIDATION_ERROR: 400,
            ErrorCode.INTERNAL_ERROR: 500,
        }
        return mapping[self]
    
    @property
    def message(self) -> str:
        messages = {
            ErrorCode.NOT_FOUND: "Resource not found",
            ErrorCode.UNAUTHORIZED: "Authentication required",
            ErrorCode.VALIDATION_ERROR: "Invalid input",
            ErrorCode.INTERNAL_ERROR: "Internal server error",
        }
        return messages[self]

Serialization

from enum import Enum
import json
 
class Status(str, Enum):
    ACTIVE = "active"
    INACTIVE = "inactive"
 
# JSON encode
data = {"status": Status.ACTIVE}
json.dumps(data)  # '{"status": "active"}'
 
# Custom encoder for non-string enums
class EnumEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Enum):
            return obj.value
        return super().default(obj)
 
class Priority(Enum):
    LOW = 1
    HIGH = 2
 
json.dumps({"priority": Priority.HIGH}, cls=EnumEncoder)
# '{"priority": 2}'

Enums make your code self-documenting and catch errors at the type level. Use them anywhere you have a fixed set of related constants.

React to this post: