Enumerations (Enums) are a powerful Python feature that lets you define fixed sets of named values. Since their introduction in Python 3.4 via PEP 435, the enum module has become essential for writing more expressive, safer, and maintainable code.
In this complete guide, you will learn everything from basic concepts to advanced Enum techniques in Python, with practical examples you can apply immediately in your projects.
🔷 What Are Enumerations?
An enumeration is a data type consisting of a fixed set of named values called members. Instead of using loose strings or magic numbers scattered throughout your code, you define an Enum that centralizes and gives meaning to those values.
For instance, instead of using strings like "monday", "tuesday" that are prone to typos, you can create:
from enum import Enum
class Weekday(Enum):
MONDAY = 1
TUESDAY = 2
WEDNESDAY = 3
THURSDAY = 4
FRIDAY = 5
SATURDAY = 6
SUNDAY = 7
Now, instead of comparing error-prone strings, you use Weekday.MONDAY, ensuring only valid values are used.
📖 Why Use Enum?
Enumerations bring several benefits to your code:
- Readability: meaningful names replace magic numbers or loose strings
- Safety: prevents typos and invalid values
- Maintainability: centralized changes in a single location
- Self-documentation: the code clearly expresses its intent
- Iteration: you can easily loop through all members
- Comparison: members can be compared by identity
According to the official Python documentation, Enums are ideal for representing fixed values like states, categories, directions, colors, and any finite set of options.
🚀 Creating Your First Enum
Let's create an Enum to represent order status in an e-commerce system:
from enum import Enum
class OrderStatus(Enum):
PENDING = 1
CONFIRMED = 2
SHIPPED = 3
DELIVERED = 4
CANCELLED = 5
Accessing members
print(OrderStatus.PENDING) # OrderStatus.PENDING
print(OrderStatus.PENDING.name) # 'PENDING'
print(OrderStatus.PENDING.value) # 1
Each Enum member has two main attributes:
.name: returns the member name as a string.value: returns the value assigned to the member
You can also create Enums using the Functional API, which is handy for simple Enums:
from enum import Enum
Color = Enum('Color', ['RED', 'BLUE', 'GREEN'])
print(Color.RED) # Color.RED
print(list(Color)) # [Color.RED, Color.BLUE, Color.GREEN]
🎯 Accessing Members by Name and Value
Python lets you access members both by name and by value:
from enum import Enum
class OrderStatus(Enum):
PENDING = 1
CONFIRMED = 2
Access by name
status = OrderStatus['PENDING']
print(status) # OrderStatus.PENDING
Access by value
status = OrderStatus(2)
print(status) # OrderStatus.CONFIRMED
This flexibility is extremely useful when integrating with databases or APIs that return numeric values.
🔄 Iterating Over Enums
Enums are iterable, allowing you to easily loop through all members:
for status in OrderStatus:
print(f"{status.name} = {status.value}")
Output:
PENDING = 1
CONFIRMED = 2
SHIPPED = 3
DELIVERED = 4
CANCELLED = 5
This is particularly useful for populating dropdowns, generating reports, or validating input.
🔧 Enum with auto()
The auto() function generates values automatically, saving you from manually numbering each member:
from enum import Enum, auto
class OrderStatus(Enum):
PENDING = auto()
CONFIRMED = auto()
SHIPPED = auto()
DELIVERED = auto()
CANCELLED = auto()
print(list(OrderStatus))
[OrderStatus.PENDING, OrderStatus.CONFIRMED, ...]
Checking generated values
for status in OrderStatus:
print(f"{status.name} = {status.value}")
PENDING = 1
CONFIRMED = 2
SHIPPED = 3
DELIVERED = 4
CANCELLED = 5
By default, auto() starts at 1 and increments by 1, but you can customize this behavior by overriding _generate_next_value_.
🏷️ IntEnum: Enum with Integer Behavior
IntEnum is a subclass of Enum that also inherits from int, allowing members to behave as integers:
from enum import IntEnum
class Priority(IntEnum):
LOW = 1
MEDIUM = 2
HIGH = 3
Can be used as an integer
print(Priority.HIGH > Priority.MEDIUM) # True
print(Priority.HIGH + Priority.LOW) # 4
Can substitute integers directly
def process_ticket(priority: Priority):
if priority >= Priority.HIGH:
print("High priority! Process immediately.")
process_ticket(3) # Works! IntEnum accepts int comparison
IntEnum is perfect when you need members to work in contexts that expect integers, such as list indices or function parameters requiring numbers.
🚩 Flag: Enum for Combining Values
Flag is ideal for representing combinations of options using bitwise operations:
from enum import Flag, auto
class Permission(Flag):
READ = auto()
WRITE = auto()
EXECUTE = auto()
DELETE = auto()
Combining permissions
admin_perm = Permission.READ | Permission.WRITE | Permission.EXECUTE | Permission.DELETE
user_perm = Permission.READ | Permission.WRITE
Checking permissions
if Permission.READ in user_perm:
print("User can read")
if Permission.DELETE not in user_perm:
print("User CANNOT delete")
Inspecting members
print(Permission.READ) # Permission.READ
print(Permission.READ.value) # 1 (bits: 0001)
Flag is extremely useful for permission systems, feature toggles, filters, and any scenario where multiple options can be active simultaneously.
🧩 StrEnum: Enum with String Behavior
StrEnum (available since Python 3.11) combines Enum with str:
from enum import StrEnum
class Color(StrEnum):
RED = 'red'
BLUE = 'blue'
GREEN = 'green'
Can be used as a string
print(Color.RED.upper()) # 'RED'
print(f"Color is {Color.BLUE}") # 'Color is blue'
print(Color.RED in 'red apple') # True
StrEnum is perfect for integrating with databases, REST APIs, and configuration files where strings are the standard data exchange format.
🧠 Methods and Properties in Enums
Enums can have custom methods and properties, making them even more powerful:
from enum import Enum
class OrderStatus(Enum):
PENDING = 1
CONFIRMED = 2
SHIPPED = 3
DELIVERED = 4
CANCELLED = 5
@property
def description(self):
descriptions = {
1: 'Awaiting confirmation',
2: 'Payment confirmed',
3: 'Out for delivery',
4: 'Delivered to customer',
5: 'Order cancelled',
}
return descriptions[self.value]
def is_final(self):
return self in (OrderStatus.DELIVERED, OrderStatus.CANCELLED)
Using methods and properties
status = OrderStatus.SHIPPED
print(status.description) # 'Out for delivery'
print(status.is_final()) # False
status = OrderStatus.DELIVERED
print(status.is_final()) # True
print(status.description) # 'Delivered to customer'
This approach keeps related logic centralized within the Enum, following the principle of cohesion and making maintenance easier.
🔍 Comparison and Identity
Enum members are singletons, meaning there is only one instance of each member in memory:
from enum import Enum
class OrderStatus(Enum):
PENDING = 1
CONFIRMED = 2
Identity comparison (recommended)
print(OrderStatus.PENDING is OrderStatus.PENDING) # True
Equality comparison
print(OrderStatus.PENDING == OrderStatus.PENDING) # True
print(OrderStatus.PENDING == OrderStatus.CONFIRMED) # False
Comparing with value (NOT recommended - only works by accident)
print(OrderStatus.PENDING == 1) # False! (Except for IntEnum)
Always use is or == to compare members of the same Enum. Avoid comparing directly with values unless you are using IntEnum.
🛡️ Unique Values and @unique
By default, Python allows duplicate values in Enums (duplicate names become aliases). Use the @unique decorator to ensure all values are unique:
from enum import Enum, unique
@unique
class OrderStatus(Enum):
PENDING = 1
CONFIRMED = 2
SHIPPED = 2 # This would raise ValueError!
SHIPPED = 3
Duplicate values are allowed without @unique
class ColorNoUnique(Enum):
RED = 1
DARK_RED = 1 # Alias for RED
print(ColorNoUnique(1)) # ColorNoUnique.RED (first one is canonical)
Use @unique whenever your Enum semantics require each value to be unique. This prevents subtle bugs caused by accidental aliases.
📋 Aliases in Enums
Aliases are different names that point to the same value. They can be useful for providing alternative names:
from enum import Enum
class Color(Enum):
RED = 1
ROJO = 1 # Spanish alias
BLUE = 2
AZUL = 2
print(Color.RED) # Color.RED
print(Color.ROJO) # Color.RED (alias)
print(Color(1)) # Color.RED (first name is canonical)
List only unique members (without aliases)
print(list(Color))
[Color.RED, Color.BLUE]
Aliases are especially useful for maintaining compatibility with legacy systems or providing names in multiple languages.
💡 Best Practices with Enums
- Always use UPPERCASE names: by convention, Enum members are named in uppercase letters
- Use auto() when possible: avoids manual numbering errors
- Prefer @unique: guarantees no accidentally duplicated values
- Use Enum instead of loose strings or ints: for any fixed set of options
- Combine with Type Hints: static typing + Enums = extremely safe code
- Handle unknown values: when receiving external data, use
try/except ValueErrorto catch invalid values
⚠️ Error Handling with Enums
When receiving external values (from APIs, databases, or forms), it is crucial to handle invalid input:
from enum import Enum
class OrderStatus(Enum):
PENDING = 1
CONFIRMED = 2
SHIPPED = 3
DELIVERED = 4
CANCELLED = 5
def get_status(code: int):
try:
return OrderStatus(code)
except ValueError:
return None # Or raise a custom exception
Testing
print(get_status(3)) # OrderStatus.SHIPPED
print(get_status(99)) # None
This technique integrates seamlessly with Python's error handling system, ensuring your code gracefully handles unexpected input.
🌐 Enums and Databases
Enums are excellent for representing columns with fixed values in databases. Here is how to integrate with SQLAlchemy:
from enum import Enum
import sqlalchemy as sa
from sqlalchemy.orm import declarative_base, Mapped, mapped_column
class OrderStatus(Enum):
PENDING = 1
CONFIRMED = 2
SHIPPED = 3
DELIVERED = 4
CANCELLED = 5
Base = declarative_base()
class Order(Base):
tablename = 'orders'
id: Mapped[int] = mapped_column(primary_key=True)
status: Mapped[OrderStatus] = mapped_column(sa.Enum(OrderStatus))
This ensures referential integrity at both the application and database levels simultaneously.
📦 Enums in the Real World
Enums are widely used in real-world Python projects. Major frameworks like Django use Enums extensively in their models and configurations. The official Enum HOWTO shows advanced examples with OrderedEnum and other patterns.
Here is a more complex example of a logging system with levels:
from enum import IntEnum
from datetime import datetime
class LogLevel(IntEnum):
DEBUG = 10
INFO = 20
WARNING = 30
ERROR = 40
CRITICAL = 50
def format(self, message: str) -> str:
timestamp = datetime.now().isoformat()
return f"[{timestamp}] [{self.name}] {message}"
Usage
log = LogLevel.INFO.format("System started successfully")
print(log)
[2026-05-22T09:00:00] [INFO] System started successfully
📖 Recommended Reading
- Official enum module documentation - The definitive reference
- Enum HOWTO - Official practical guide
- PEP 435 - The original Enum specification for Python
- Real Python: Python Enum - Comprehensive tutorial
- Enum.auto() - Documentation on automatic values
- IntEnum - Enum with integer behavior
- Flag - Enum for bitwise combinations
- Python Glossary: Enum - Definition in the official glossary
✅ Conclusion
Python Enumerations are much more than simple constant lists. They provide an elegant and safe way to represent fixed sets of values, making your code more readable, maintainable, and less error-prone.
From the basics with Enum and auto() to powerful variants like IntEnum, Flag, and StrEnum, the enum module offers tools for virtually any scenario.
Now that you master Enums in Python, start applying them in your projects. Replace those loose strings and magic numbers with well-defined enumerations. Your code (and your teammates) will thank you! 🐍