Python - Enum Class with Examples

Python's `enum` module provides a way to create enumerated constants that are both type-safe and self-documenting. Unlike simple string or integer constants, enums create distinct types that prevent...

Key Insights

  • Enums provide type-safe constants that prevent invalid values and improve code readability by replacing magic numbers and strings with meaningful names
  • Python’s Enum class supports value assignment, automatic value generation, functional API creation, and advanced features like unique value enforcement and flag combinations
  • Proper enum usage enables exhaustive pattern matching with match statements, seamless JSON serialization, and integration with type checkers for compile-time validation

Understanding Python Enums

Python’s enum module provides a way to create enumerated constants that are both type-safe and self-documenting. Unlike simple string or integer constants, enums create distinct types that prevent accidental misuse and make code intentions explicit.

from enum import Enum

class Status(Enum):
    PENDING = 1
    APPROVED = 2
    REJECTED = 3
    CANCELLED = 4

# Usage
order_status = Status.PENDING
print(order_status)  # Status.PENDING
print(order_status.name)  # 'PENDING'
print(order_status.value)  # 1

Enums prevent invalid assignments that would pass silently with plain constants:

# Without enums - error-prone
STATUS_PENDING = 1
current_status = 5  # Invalid but no error

# With enums - type-safe
current_status = Status.PENDING
# current_status = 5  # TypeError: not a valid Status

Auto-Generated Values

Use auto() to automatically assign values when the specific value doesn’t matter:

from enum import Enum, auto

class Priority(Enum):
    LOW = auto()      # 1
    MEDIUM = auto()   # 2
    HIGH = auto()     # 3
    CRITICAL = auto() # 4

class Permission(Enum):
    READ = auto()     # 1
    WRITE = auto()    # 2
    EXECUTE = auto()  # 4
    DELETE = auto()   # 8

Customize auto-generation by overriding _generate_next_value_:

from enum import Enum

class HttpStatus(Enum):
    def _generate_next_value_(name, start, count, last_values):
        return name.lower()
    
    OK = auto()              # 'ok'
    NOT_FOUND = auto()       # 'not_found'
    SERVER_ERROR = auto()    # 'server_error'

String and Integer Enums

Mix enum with built-in types for specialized behavior:

from enum import IntEnum, StrEnum

class ErrorCode(IntEnum):
    SUCCESS = 0
    NOT_FOUND = 404
    SERVER_ERROR = 500
    
    def is_error(self):
        return self.value >= 400

# IntEnum members are integers
code = ErrorCode.NOT_FOUND
if code > 200:  # Works with integer comparison
    print(f"Error: {code}")

class Environment(StrEnum):
    DEVELOPMENT = "dev"
    STAGING = "staging"
    PRODUCTION = "prod"

# StrEnum members are strings
env = Environment.PRODUCTION
config_file = f"config.{env}.json"  # "config.prod.json"

Iteration and Membership

Enums are iterable and support membership testing:

from enum import Enum

class Color(Enum):
    RED = "#FF0000"
    GREEN = "#00FF00"
    BLUE = "#0000FF"
    YELLOW = "#FFFF00"

# Iterate over enum members
for color in Color:
    print(f"{color.name}: {color.value}")

# Membership testing
print(Color.RED in Color)  # True
print("RED" in Color.__members__)  # True

# Access by name or value
color = Color["RED"]
color = Color("#FF0000")  # ValueError - use explicit lookup

# Safe value lookup
def get_color_by_hex(hex_code):
    for color in Color:
        if color.value == hex_code:
            return color
    return None

Unique Value Enforcement

Use @unique decorator to ensure no duplicate values:

from enum import Enum, unique

@unique
class DatabaseType(Enum):
    POSTGRESQL = "postgres"
    MYSQL = "mysql"
    SQLITE = "sqlite"
    # POSTGRES = "postgres"  # ValueError: duplicate value

# Without @unique, aliases are allowed
class Status(Enum):
    PENDING = 1
    WAITING = 1  # Alias for PENDING
    APPROVED = 2

print(Status.PENDING is Status.WAITING)  # True

Functional API

Create enums dynamically using the functional API:

from enum import Enum

# Simple creation
Animal = Enum('Animal', ['CAT', 'DOG', 'BIRD'])

# With custom values
HttpMethod = Enum('HttpMethod', {
    'GET': 1,
    'POST': 2,
    'PUT': 3,
    'DELETE': 4
})

# From list with auto values
Weekday = Enum('Weekday', 
    ['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY'],
    start=1
)

print(Weekday.MONDAY.value)  # 1

Flag Enums for Bit Operations

Use Flag for combinable bit flags:

from enum import Flag, auto

class FilePermission(Flag):
    READ = auto()     # 1
    WRITE = auto()    # 2
    EXECUTE = auto()  # 4
    
    RW = READ | WRITE
    RWX = READ | WRITE | EXECUTE

# Combine flags
perms = FilePermission.READ | FilePermission.WRITE
print(perms)  # FilePermission.RW

# Check flags
if FilePermission.READ in perms:
    print("Has read permission")

# Remove flags
perms = perms & ~FilePermission.WRITE
print(perms)  # FilePermission.READ

Pattern Matching with Enums

Python 3.10+ match statements work excellently with enums:

from enum import Enum

class OrderStatus(Enum):
    PENDING = "pending"
    PROCESSING = "processing"
    SHIPPED = "shipped"
    DELIVERED = "delivered"
    CANCELLED = "cancelled"

def handle_order(status: OrderStatus) -> str:
    match status:
        case OrderStatus.PENDING:
            return "Awaiting payment"
        case OrderStatus.PROCESSING:
            return "Preparing shipment"
        case OrderStatus.SHIPPED:
            return "In transit"
        case OrderStatus.DELIVERED:
            return "Order complete"
        case OrderStatus.CANCELLED:
            return "Order cancelled"

print(handle_order(OrderStatus.SHIPPED))  # "In transit"

Enum Methods and Properties

Add custom methods and properties to enum classes:

from enum import Enum
from datetime import timedelta

class SubscriptionTier(Enum):
    FREE = (0, 30)
    BASIC = (9.99, 365)
    PREMIUM = (19.99, 365)
    ENTERPRISE = (49.99, 365)
    
    def __init__(self, price, trial_days):
        self.price = price
        self.trial_days = trial_days
    
    @property
    def trial_period(self):
        return timedelta(days=self.trial_days)
    
    def calculate_annual_cost(self):
        return self.price * 12
    
    @classmethod
    def get_paid_tiers(cls):
        return [tier for tier in cls if tier.price > 0]

tier = SubscriptionTier.PREMIUM
print(f"Price: ${tier.price}/month")
print(f"Annual: ${tier.calculate_annual_cost()}")
print(f"Trial: {tier.trial_period.days} days")

JSON Serialization

Handle enum serialization for APIs:

from enum import Enum
import json

class Status(Enum):
    ACTIVE = "active"
    INACTIVE = "inactive"
    SUSPENDED = "suspended"

class EnumEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Enum):
            return obj.value
        return super().default(obj)

data = {
    "user_id": 123,
    "status": Status.ACTIVE
}

# Serialize
json_str = json.dumps(data, cls=EnumEncoder)
print(json_str)  # {"user_id": 123, "status": "active"}

# Deserialize
def decode_status(data):
    if "status" in data:
        data["status"] = Status(data["status"])
    return data

parsed = json.loads(json_str, object_hook=decode_status)
print(type(parsed["status"]))  # <enum 'Status'>

Type Hints and Validation

Use enums with type hints for better IDE support and runtime validation:

from enum import Enum
from typing import Literal

class LogLevel(Enum):
    DEBUG = 10
    INFO = 20
    WARNING = 30
    ERROR = 40
    CRITICAL = 50

def log_message(message: str, level: LogLevel) -> None:
    if level.value >= LogLevel.WARNING.value:
        print(f"[{level.name}] {message}")

# Type checker catches invalid usage
log_message("System starting", LogLevel.INFO)
# log_message("Error", "ERROR")  # Type error

# Validate user input
def parse_log_level(level_str: str) -> LogLevel:
    try:
        return LogLevel[level_str.upper()]
    except KeyError:
        valid = [level.name for level in LogLevel]
        raise ValueError(f"Invalid level. Choose from: {valid}")

user_input = "warning"
level = parse_log_level(user_input)

Enums transform magic values into self-documenting, type-safe constants. They integrate seamlessly with Python’s type system, pattern matching, and serialization needs, making them essential for production code that values clarity and correctness.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.