Python enums help you replace fragile strings and unexplained numbers with named, reliable values. When a program has a fixed set of states, roles, actions, colors, categories, permissions, or modes, plain strings can become dangerous. One typo such as "processing" instead of "PROCESSING" can silently break logic. An enum gives those values a formal structure, making your code easier to read, safer to maintain, and less likely to fail because of magic values.
This English version is adapted for developers who want a practical guide, not a literal translation. You will learn what enums are, how to create an Enum class, how to compare enum members, how to use auto(), when to use string enums, how to serialize enums for APIs and databases, and which mistakes to avoid. If you are still learning the foundation, start with Python for beginners and this guide to variables in Python.
What Is an Enum in Python?
An enum, short for enumeration, is a type that defines a fixed group of named members. Each member has a name and a value. Instead of scattering raw strings or numbers through your code, you define the allowed options once and then refer to them by name. Python provides official enum support through the built-in enum module, so you do not need to install an external package.
The official Python enum documentation describes the module as support for enumerations, a set of symbolic names bound to unique values. The concept is useful whenever a value should belong to a known, limited set. Order statuses, user roles, payment methods, log levels, file states, and application modes are common examples.
The Problem with Magic Strings and Magic Numbers
Before enums, many developers represent fixed choices with strings or integers. That works for small scripts, but it becomes fragile in larger codebases. Strings are easy to mistype. Numbers are hard to understand without context. If a function receives 3, what does that mean? Is it a status, priority, role, or category? Without a named abstraction, the meaning lives only in the developer’s memory.
# Fragile approach
status = "paid"
if status == "payed": # typo: this condition never matches
send_receipt()This is the kind of bug that may not raise a syntax error. The program runs, but the logic is wrong. Enums help because code completion, comparisons, and centralized definitions make mistakes easier to spot. If you are studying common failures, this guide to Python syntax errors is useful, but enums mainly prevent logical mistakes rather than syntax mistakes.
Create Your First Python Enum
To create an enum, import Enum from the enum module and define a class that inherits from it. The members are usually written in uppercase because they behave like named constants.
from enum import Enum
class OrderStatus(Enum):
PENDING = 1
PAID = 2
SHIPPED = 3
DELIVERED = 4Now OrderStatus.PAID is not just a string or number. It is a specific member of the OrderStatus enum. That makes your code more explicit. Anyone reading the code understands that only the statuses defined in OrderStatus should be used. This is especially helpful when your code grows into multiple modules, services, or teams.
Access Names and Values
Every enum member has a name and a value. The name is the symbolic identifier written in the class. The value is what you assign to it. You can access both when you need to display, log, serialize, or compare data.
status = OrderStatus.PAID
print(status.name) # PAID
print(status.value) # 2
print(status) # OrderStatus.PAIDYou can also recover a member from its value. This is useful when you receive an integer from a database or external service and want to convert it back to a typed enum member.
status = OrderStatus(2)
print(status is OrderStatus.PAID)If the value does not exist, Python raises ValueError. That is a good thing: invalid states should fail early instead of quietly flowing through the program. If you are handling invalid inputs, review try and except in Python.
Compare Enum Members Correctly
Enum members are singletons, so identity comparison with is is common and expressive. Equality with == also works. What you should avoid is comparing enum members directly to their raw values unless you have a clear reason. The point of using enums is to keep the code working with meaningful named members.
def can_ship(status: OrderStatus) -> bool:
return status is OrderStatus.PAID
print(can_ship(OrderStatus.PAID))This function is easier to understand than one that checks whether status == 2. The enum name explains the business rule. Type hints also make the function easier to use with modern editors and static analysis tools. For more on annotations, read this guide to Python type hints.
Use auto() When Values Do Not Matter
Sometimes the numeric value behind each enum member does not matter. You only need unique symbolic names. In that case, use auto(). Python automatically assigns values for you, reducing repetitive code and avoiding accidental duplicate values.
from enum import Enum, auto
class TaskState(Enum):
CREATED = auto()
RUNNING = auto()
FAILED = auto()
FINISHED = auto()Use auto() when the value is an implementation detail. Do not use it if the value must be stable for a database, API, configuration file, or external integration. If external systems depend on the value, choose explicit values and document them.
String Enums for APIs and JSON
Many real applications exchange data as JSON. In that context, string values are often easier to read and more stable than integers. You can create enum members whose values are strings. This makes API payloads understandable while still keeping Python code type-safe and centralized.
from enum import Enum
class PaymentMethod(Enum):
CREDIT_CARD = "credit_card"
PIX = "pix"
BANK_SLIP = "bank_slip"When serializing to JSON, use the .value attribute. When receiving a string back from an API, pass it to the enum class to validate it. Invalid strings will raise an error, giving you a clean place to reject bad input. This approach is much safer than spreading string literals across controllers, services, and tests.
Loop Through Enum Members
Enums are iterable. You can loop through all members to build choices for a form, print documentation, validate mappings, or generate dropdown options. This avoids duplicating the same list of possible values in multiple places.
for status in OrderStatus:
print(status.name, status.value)This is one of the practical benefits of centralizing choices. If you add a new member later, code that iterates over the enum can automatically include it. This is cleaner than maintaining separate lists. If loops are still new, review this article on for loops in Python.
Enums vs Dictionaries and Constants
Could you use dictionaries or module-level constants instead of enums? Sometimes, yes. A simple constant is enough when there is only one fixed value. A dictionary is useful when you need mappings. But enums are better when you need a closed set of valid options that should be named, comparable, iterable, and validated.
For example, a dictionary can map statuses to labels, but it does not prevent someone from passing an invalid status string. An enum gives you a type-level concept. You can still combine enums with dictionaries when you need metadata:
STATUS_LABELS = {
OrderStatus.PENDING: "Waiting for payment",
OrderStatus.PAID: "Payment confirmed",
OrderStatus.SHIPPED: "On the way",
OrderStatus.DELIVERED: "Delivered",
}This is safer than using raw strings as dictionary keys because the keys must be valid enum members. If you work often with mappings, this guide to Python dictionaries is a useful reference.
Enums in Databases
When storing enums in a database, choose a representation intentionally. You can store the enum name, the enum value, or a separate stable code. Storing names is readable but can break if you rename members. Storing numeric values is compact but less clear. Storing explicit string values is often a good compromise for application data because it is both readable and stable.
# Store this in the database
status_code = OrderStatus.PAID.name
# Restore it later
status = OrderStatus[status_code]For APIs, string values are usually preferred. For internal systems, names may be acceptable. The main rule is consistency: choose one approach and use it everywhere. Migration problems often appear when one part of the app stores names and another expects values.
Common Mistakes with Python Enums
The first mistake is using enums for values that are not fixed. If users can create new categories at runtime, those categories belong in a database table, not in an enum class. Enums are best for sets controlled by the codebase. The second mistake is comparing enum members to raw strings or numbers everywhere. That defeats the purpose of the abstraction.
The third mistake is changing enum values without thinking about persisted data. If you store enum values in a database or API payload, changing a value is a breaking change. The fourth mistake is overusing enums where a simple boolean or constant would be clearer. Use enums when they clarify a real set of possible states, not because they look more advanced.
When Should You Use Enums?
Use enums when a variable should have one value from a known finite set. Good examples include order status, account role, notification type, export format, environment name, task state, permission level, and payment method. These values appear in business rules, tests, logs, APIs, and user interfaces, so giving them a central definition reduces duplication and confusion.
A simple rule works well: if you find the same string or number repeated in several files to represent a concept, consider an enum. If you find yourself explaining what the number 4 means, consider an enum. If autocomplete and validation would make the code safer, consider an enum.
A Practical Pattern for Applications
In real applications, place enums close to the domain they describe. For example, order-related enums may live in an orders module. Keep names explicit and values stable. Add helper functions only when they truly belong to the enum. Avoid turning enum classes into large containers for unrelated behavior.
from enum import Enum
class OrderStatus(Enum):
PENDING = "pending"
PAID = "paid"
SHIPPED = "shipped"
DELIVERED = "delivered"
def can_be_cancelled(self) -> bool:
return self in {OrderStatus.PENDING, OrderStatus.PAID}This pattern keeps business logic near the state definition without making the enum too heavy. It also makes tests easier because the allowed states are easy to discover and compare.
Final Checklist
Use enums to model fixed sets of named values. Prefer meaningful names over raw strings or numbers. Use auto() only when values do not need to be stable. Use explicit string values for APIs and JSON. Compare enum members, not raw values, inside your business logic. Be careful when changing persisted enum values. Avoid enums for dynamic user-created categories.
Python enums are a small feature with a large impact on code quality. They make invalid states harder to represent, reduce typo-based bugs, and help your code communicate intent. When used in the right places, they are one of the simplest ways to make Python applications safer and easier to maintain.




