Python’s enum module provides a way to define sets of symbolic names bound to unique, constant values. Before enums were available, developers typically used plain integers or class-level string constants — approaches that offer no type safety, no meaningful repr(), and no protection against accidental value reassignment.
Since Python 3.4 introduced the enum module via PEP 435,
enumerations have become a standard pattern for representing a fixed set of
related values, such as days of the week, HTTP methods, status codes, or
permission levels.
This guide covers everything you need to know about Python
enums: how to create them, which type to choose, how auto() and @unique work,
and how to apply them in real-world scenarios including Django models and REST
APIs.
|
Key Takeaways |
|
1. Python enums are defined by subclassing Enum (or a mixin 2. Five main types exist: Enum, IntEnum, StrEnum (3.11+), 3. auto() assigns values automatically; @unique prevents 4. Enum members are immutable, iterable, and support 5. Real-world use cases include Django model choices, API |
What Is a Python Enum?
An enumeration (enum) is a set of symbolic names, called
members, that are bound to unique, constant values. The Python documentation
describes enums as similar to global variables, but with a more useful repr(),
grouping, type-safety, and other features.
Enums are part of the standard library’s enum module and do
not require any third-party installation.
Why Use Enums Instead of Constants?
Consider a typical pre-enum pattern:
# Old pattern
— fragile
STATUS_PENDING
= 0
STATUS_ACTIVE
= 1
STATUS_INACTIVE
= 2
This approach has several drawbacks:
•
No type safety — any integer is valid, not just 0, 1, or 2.
•
No grouping — the constants are scattered in the module namespace.
•
Poor repr() — print(STATUS_ACTIVE) outputs 1, not something descriptive.
•
Reassignment risk — STATUS_ACTIVE = 99 is silently accepted.
The enum equivalent resolves all of these:
from enum
import Enum
class
Status(Enum):
PENDING =
0
ACTIVE =
1
INACTIVE =
2
print(Status.ACTIVE)
# Status.ACTIVE
print(Status.ACTIVE.value)
# 1
print(Status.ACTIVE.name)
# ‘ACTIVE’
Note: The enum module was added to Python’s standard
library in Python 3.4 (PEP 435). It is not available in Python 2 without the
separately maintained enum34 backport.
Creating a Python Enum
The most common way to define an enum is to subclass Enum from
the enum module.
Class-Based Definition
from enum
import Enum
class
Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
Each name (RED, GREEN, BLUE) is an enum member. Each member
has two key attributes:
•
.name — the member’s string name (‘RED’, ‘GREEN’, ‘BLUE’)
•
.value — the assigned value (1, 2, 3)
Functional (Constructor) API
Python’s enum module also supports a functional API, useful
for creating enums dynamically:
from enum
import Enum
Color =
Enum(‘Color’, [‘RED’, ‘GREEN’, ‘BLUE’])
# Values are
automatically assigned: RED=1, GREEN=2, BLUE=3
You may also pass a string of space-separated names:
Direction =
Enum(‘Direction’, ‘NORTH SOUTH EAST WEST’)
Tip: The functional API is convenient for dynamically
generated enums, but the class-based syntax is generally preferred for
readability and IDE support.
Accessing and Using Enum Members
By Name and By Value
# By attribute
access
print(Color.RED)
# Color.RED
# By name
(string lookup)
print(Color[‘RED’])
# Color.RED
# By value
print(Color(1))
# Color.RED
Iterating Over Members
for color in
Color:
print(color.name, color.value)
# RED 1
# GREEN 2
# BLUE 3
Membership Testing
print(Color.RED
in Color) # True
Comparison
Enum members compare by identity, not by value. Two members
are equal only if they are the same member:
Color.RED ==
Color.RED # True
Color.RED ==
Color.GREEN # False
Color.RED ==
1 # False (use IntEnum if you need this)
Note: Regular Enum members do not compare equal to their
raw values (e.g., Color.RED != 1). If you need integer comparison, use IntEnum
instead.
Using auto() for Automatic Values
The auto() helper assigns values automatically so you don’t
need to specify them manually. The default behaviour depends on the enum base
class.
from enum
import Enum, auto
class
Direction(Enum):
NORTH =
auto()
SOUTH =
auto()
EAST =
auto()
WEST =
auto()
print(list(Direction))
#
[<Direction.NORTH: 1>, <Direction.SOUTH: 2>, <Direction.EAST:
3>, <Direction.WEST: 4>]
auto() Behaviour by Enum Type
|
Enum Base |
auto() |
Notes |
|
Enum |
1, 2, 3 … |
Increments from |
|
StrEnum |
Lowercase |
‘NORTH’ becomes |
|
Flag |
Powers of 2 |
Ready for |
|
Custom |
User-defined |
Override to |
Customising auto() with _generate_next_value_
You can override _generate_next_value_ to control what auto()
returns:
from enum
import Enum, auto
class
UpperEnum(Enum):
@staticmethod
def
_generate_next_value_(name, start, count, last_values):
return
name.upper()
class
Color(UpperEnum):
red =
auto()
green =
auto()
print(Color.red.value)
# ‘RED’
Preventing Duplicate Values with @unique
By default, assigning the same value to two members creates an
alias — the second name points to the first member. If you want every member to
have a distinct value, use the @unique decorator:
from enum
import Enum, unique
@unique
class
Status(Enum):
PENDING =
1
ACTIVE =
1 # <– raises ValueError: duplicate values found
Without @unique, the duplicate would silently become an alias:
class
Status(Enum):
PENDING =
1
WAITING =
1 # alias for PENDING
Status.WAITING
is Status.PENDING # True
All Python Enum Types Compared
Python’s enum module provides several specialised base classes
beyond the default Enum. Choosing the right one typically depends on the type
of values involved and the operations you need to perform.
|
Type |
Inherits |
Values Are |
Added In |
Best For |
|
Enum |
object |
Any type |
Python 3.4 |
General-purpose |
|
IntEnum |
int |
Integers |
Python 3.4 |
Numeric |
|
StrEnum |
str |
Strings |
Python 3.11 |
String |
|
Flag |
Enum |
Powers of 2 |
Python 3.6 |
Bitwise flags, |
|
IntFlag |
int + Flag |
Integers |
Python 3.6 |
Bitwise flags |
IntEnum — Integer-Compatible Enumerations
IntEnum inherits from both int and Enum. Members behave like
integers in comparisons and arithmetic, making them suitable for numeric APIs,
C interoperability, and situations where you need enum members to interoperate
with plain integers.
from enum
import IntEnum
class
Priority(IntEnum):
LOW = 1
MEDIUM = 2
HIGH = 3
print(Priority.HIGH
> Priority.LOW) # True
print(Priority.HIGH
> 2) # True (int comparison works)
print(Priority.HIGH
+ 1) # 4 (arithmetic works)
Note: Because IntEnum members compare equal to integers,
they lose some of the type-safety that plain Enum provides. Use IntEnum only
when integer interoperability is genuinely required.
StrEnum — String-Compatible Enumerations (Python 3.11+)
Introduced in Python 3.11, StrEnum inherits from both str and
Enum. Members are strings, and auto() automatically assigns the lowercase
version of the member name.
from enum
import StrEnum, auto # Python 3.11+
class
HTTPMethod(StrEnum):
GET =
auto()
POST =
auto()
PUT =
auto()
DELETE =
auto()
print(HTTPMethod.GET)
# HTTPMethod.GET
print(HTTPMethod.GET.value)
# ‘get’
print(HTTPMethod.GET
== ‘get’) # True (str comparison works)
StrEnum is particularly useful for:
•
JSON serialisation where values must be plain strings.
•
HTTP status or method labels in REST APIs.
•
Django or database field choices.
StrEnum Before Python 3.11
If you are on Python 3.10 or earlier, you can approximate
StrEnum by inheriting from both str and Enum:
# Python 3.10
and earlier workaround
class
StrEnum(str, Enum):
pass
class
Color(StrEnum):
RED =
‘red’
GREEN =
‘green’
Flag and IntFlag — Combinable Bitwise Enumerations
Flag and IntFlag are designed for permission-style or
option-flag scenarios where multiple values can be combined using bitwise
operators (|, &, ^, ~). Values should generally be powers of 2 (1, 2, 4, 8,
…) to avoid overlapping bits. auto() handles this automatically for Flag.
Flag Example
from enum
import Flag, auto
class
Permission(Flag):
READ =
auto() # 1
WRITE =
auto() # 2
EXECUTE =
auto() # 4
# Combining
flags
user_perms =
Permission.READ | Permission.WRITE
print(user_perms)
# Permission.READ|WRITE
# Testing for
a flag
print(Permission.READ
in user_perms) # True
print(Permission.EXECUTE
in user_perms) # False
IntFlag Example
IntFlag works like Flag but also inherits from int, allowing
members to be used in integer contexts:
from enum
import IntFlag, auto
class
FileMode(IntFlag):
READ =
4
WRITE =
2
EXECUTE =
1
mode =
FileMode.READ | FileMode.WRITE
print(int(mode))
# 6
Adding Methods to Enum Classes
You can define methods on an Enum class. Every member will
have access to those methods, which is useful for adding behaviour directly to
the enum.
from enum
import Enum
class
Planet(Enum):
MERCURY =
(3.303e+23, 2.4397e6)
VENUS =
(4.869e+24, 6.0518e6)
EARTH =
(5.976e+24, 6.37814e6)
def
__init__(self, mass, radius):
self.mass = mass
self.radius = radius
@property
def
surface_gravity(self):
G =
6.67430e-11
return
G * self.mass / (self.radius ** 2)
print(Planet.EARTH.surface_gravity)
# ~9.802
Tip: Defining __str__ or __repr__ inside an Enum lets you
control exactly how members are displayed, which is useful for logging and
debugging.
Real-World Use Cases
1. Django Model Choices
Django encourages enum-based choices for CharField and
IntegerField. StrEnum (or the str + Enum pattern) integrates cleanly:
from django.db
import models
from enum
import StrEnum, auto
class
OrderStatus(StrEnum):
PENDING
= auto()
CONFIRMED
= auto()
SHIPPED
= auto()
DELIVERED
= auto()
class
Order(models.Model):
status =
models.CharField(
max_length=20,
choices=[(s, s.value) for s in OrderStatus],
default=OrderStatus.PENDING,
)
2. REST API Status Codes
from enum
import IntEnum
class
HTTPStatus(IntEnum):
OK = 200
CREATED = 201
BAD_REQUEST = 400
UNAUTHORIZED = 401
NOT_FOUND
= 404
INTERNAL_SERVER_ERR = 500
def
handle_response(status_code: int):
status =
HTTPStatus(status_code)
if status
== HTTPStatus.OK:
return
‘Success’
elif
status == HTTPStatus.NOT_FOUND:
return
‘Resource not found’
3. File Permission Flags
from enum
import IntFlag
class
Permission(IntFlag):
NONE =
0
READ =
4
WRITE =
2
EXECUTE =
1
ALL =
READ | WRITE | EXECUTE
def
check_access(user_perm: Permission, required: Permission) -> bool:
return
required in user_perm
admin =
Permission.ALL
print(check_access(admin,
Permission.WRITE)) # True
Common Mistakes and How to Avoid Them
Pitfall 1: Comparing Enum to Raw Value
# Incorrect
if status ==
1: # Always False for plain Enum
…
# Correct
if status ==
Status.ACTIVE:
…
# OR use
IntEnum if integer comparison is genuinely needed
Pitfall 2: Accidentally Creating Aliases
class
Color(Enum):
RED = 1
ROUGE =
1 # alias — not a separate member!
list(Color) #
Only [<Color.RED: 1>]
# Use @unique
to prevent this
Pitfall 3: Forgetting auto() Starts at 1, Not 0
class
Status(Enum):
PENDING =
auto() # 1, not 0
ACTIVE =
auto() # 2
Note: auto() starts at 1 by default for plain Enum. If your
code assumes 0-based indexing, assign values explicitly or override
_generate_next_value_.
Pitfall 4: Using Enum Members as Dictionary Keys with JSON
Enum members are not directly JSON-serialisable. When
serialising to JSON, use member.value:
import json
data =
{‘status’: Status.ACTIVE.value} # Correct
json.dumps(data)
# Works
#
json.dumps({‘status’: Status.ACTIVE}) raises TypeError
Python Version Notes
•
Python 3.4 — enum module introduced (PEP 435). Enum, IntEnum
available.
•
Python 3.6 — Flag and IntFlag added.
•
Python 3.6 — auto() introduced.
•
Python 3.11 — StrEnum added. enum module significantly expanded.
•
Python 3.12 — Further refinements to Flag and boundary behaviour.
Quick Reference: Which Enum Type Should You Use?
|
Goal |
Use This |
Example |
|
Named constants |
Enum |
class Color(Enum): RED=1 |
|
Integer |
IntEnum |
class Priority(IntEnum): LOW=1 |
|
String values |
StrEnum |
class Status(StrEnum): OK=auto() |
|
Combinable |
Flag / IntFlag |
class Perm(Flag): READ=auto() |
|
Auto-name |
auto() |
NORTH = auto() |
|
Prevent |
@unique |
@unique class Dir(Enum): … |
Frequently Asked Questions
|
Question |
Answer |
|
Are Enum values |
No. Enum |
|
Can two members |
Yes — by |
|
Can Enum |
Yes. Define |
|
How do I |
Use the class |
|
Is Enum |
The built-in |
|
How does Enum |
Enum provides |
Conclusion
Python enums provide a clean, safe, and expressive way to
represent fixed sets of related values. By choosing the right enum type — Enum,
IntEnum, StrEnum, Flag, or IntFlag — you can match the data type and operations
your code actually needs.
To summarise the key guidance:
•
Use Enum as your default starting point for named constants.
•
Use IntEnum when integer comparison or arithmetic is required.
•
Use StrEnum (Python 3.11+) when values must interoperate with strings.
•
Use Flag or IntFlag when values need to be combined with bitwise
operators.
•
Use auto() to avoid hardcoding values manually, and @unique to prevent
accidental aliases.
Enums generally lead to more readable, maintainable code than
bare integers or string constants, particularly as codebases grow and team
sizes increase.


