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.