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 ValueError to 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

✅ 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! 🐍