Enum vs StrEnum Differences
Overview
Python’s enum module provides several enumeration types. Enum and StrEnum are two important classes with key differences in how they handle values and string representation.
Basic Definitions
Enum
Enum is the base class for creating enumerated constants. Members can have any type of value.
StrEnum
StrEnum (introduced in Python 3.11) is a specialized Enum where all members must be strings and can be used directly as strings.
Key Differences
| Feature | Enum | StrEnum |
|---|---|---|
| Value Type | Any type (int, str, tuple, etc.) | Only strings |
| String Usage | Requires .value to use as string |
Can be used directly as string |
| Type Checking | Generic type | String-compatible type |
| Python Version | Available since Python 3.4 | Available since Python 3.11 |
| Inheritance | Base class | Inherits from Enum and str |
Basic Examples
Enum Example
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
# Usage
print(Color.RED) # Color.RED
print(Color.RED.value) # 1
print(Color.RED.name) # RED
# Cannot use directly as string
# print("Color is " + Color.RED) # TypeError!
print("Color is " + Color.RED.name) # OK: "Color is RED"
StrEnum Example
from enum import StrEnum
class Color(StrEnum):
RED = "red"
GREEN = "green"
BLUE = "blue"
# Usage
print(Color.RED) # Color.RED
print(Color.RED.value) # "red"
print(Color.RED.name) # RED
# Can use directly as string!
print("Color is " + Color.RED) # OK: "Color is red"
print(f"Color: {Color.RED}") # OK: "Color: red"
Detailed Comparison
1. Value Assignment
Enum - Can use any type:
from enum import Enum
class Status(Enum):
PENDING = 1
PROCESSING = 2
COMPLETED = 3
FAILED = "error" # Mixed types allowed
class Config(Enum):
TIMEOUT = 30
RETRIES = 3
HOSTS = ["host1", "host2"] # Even lists!
StrEnum - Must use strings:
from enum import StrEnum
class Status(StrEnum):
PENDING = "pending"
PROCESSING = "processing"
COMPLETED = "completed"
# FAILED = 1 # TypeError: values must be strings
2. String Operations
Enum - Requires explicit conversion:
from enum import Enum
class HTTPMethod(Enum):
GET = "GET"
POST = "POST"
PUT = "PUT"
method = HTTPMethod.GET
# Direct string operations don't work
# if method == "GET": # False (different types)
# pass
# Need to use .value or .name
if method.value == "GET": # OK
print("GET request")
if method.name == "GET": # OK
print("GET request")
# String concatenation requires conversion
message = "Method: " + method.value # OK
StrEnum - Direct string operations:
from enum import StrEnum
class HTTPMethod(StrEnum):
GET = "GET"
POST = "POST"
PUT = "PUT"
method = HTTPMethod.GET
# Direct string comparison works!
if method == "GET": # True
print("GET request")
# Direct string concatenation works!
message = "Method: " + method # OK: "Method: GET"
# Works in f-strings
print(f"Using {method} method") # "Using GET method"
3. Type Checking
Enum - Generic type:
from enum import Enum
from typing import Union
class Status(Enum):
ACTIVE = 1
INACTIVE = 0
def process_status(status: Status) -> None:
print(status.value)
# Type checker sees Status, not int
status: Status = Status.ACTIVE
# value: int = status # Type error
value: int = status.value # OK
StrEnum - String-compatible type:
from enum import StrEnum
class Status(StrEnum):
ACTIVE = "active"
INACTIVE = "inactive"
def process_status(status: Union[Status, str]) -> None:
print(status) # Can use directly
# Type checker sees StrEnum as string-compatible
status: Status = Status.ACTIVE
value: str = status # OK! No .value needed
4. JSON Serialization
Enum - Custom serialization needed:
from enum import Enum
import json
class Status(Enum):
ACTIVE = "active"
INACTIVE = "inactive"
# Default serialization doesn't work
# json.dumps({"status": Status.ACTIVE}) # TypeError
# Need custom encoder
class EnumEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Enum):
return obj.value
return super().default(obj)
data = {"status": Status.ACTIVE}
json_str = json.dumps(data, cls=EnumEncoder) # {"status": "active"}
StrEnum - Works with JSON directly:
from enum import StrEnum
import json
class Status(StrEnum):
ACTIVE = "active"
INACTIVE = "inactive"
# Works directly!
data = {"status": Status.ACTIVE}
json_str = json.dumps(data) # {"status": "active"} - Works!
5. Dictionary Keys and Values
Enum - Need .value:
from enum import Enum
class Priority(Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
# As dictionary key
priorities = {
Priority.LOW: 1,
Priority.MEDIUM: 2,
Priority.HIGH: 3
}
# As dictionary value - need .value for string
config = {
"default_priority": Priority.LOW.value # Need .value
}
StrEnum - Direct usage:
from enum import StrEnum
class Priority(StrEnum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
# As dictionary key
priorities = {
Priority.LOW: 1,
Priority.MEDIUM: 2,
Priority.HIGH: 3
}
# As dictionary value - direct usage!
config = {
"default_priority": Priority.LOW # No .value needed
}
Practical Examples
Example 1: API Endpoints
Using Enum:
from enum import Enum
class Endpoint(Enum):
USERS = "/api/users"
POSTS = "/api/posts"
COMMENTS = "/api/comments"
def make_request(endpoint: Endpoint):
url = f"https://api.example.com{endpoint.value}" # Need .value
# Make request...
Using StrEnum:
from enum import StrEnum
class Endpoint(StrEnum):
USERS = "/api/users"
POSTS = "/api/posts"
COMMENTS = "/api/comments"
def make_request(endpoint: Endpoint):
url = f"https://api.example.com{endpoint}" # Direct usage!
# Make request...
Example 2: Configuration Values
Using Enum:
from enum import Enum
class Environment(Enum):
DEVELOPMENT = "dev"
STAGING = "staging"
PRODUCTION = "prod"
env = Environment.DEVELOPMENT
# Need .value for string operations
database_url = f"postgresql://localhost/{env.value}"
print(f"Environment: {env.value}")
Using StrEnum:
from enum import StrEnum
class Environment(StrEnum):
DEVELOPMENT = "dev"
STAGING = "staging"
PRODUCTION = "prod"
env = Environment.DEVELOPMENT
# Direct usage!
database_url = f"postgresql://localhost/{env}"
print(f"Environment: {env}")
Example 3: File Extensions
Using Enum:
from enum import Enum
class FileType(Enum):
JSON = ".json"
XML = ".xml"
CSV = ".csv"
def get_filename(base: str, file_type: FileType) -> str:
return base + file_type.value # Need .value
Using StrEnum:
from enum import StrEnum
class FileType(StrEnum):
JSON = ".json"
XML = ".xml"
CSV = ".csv"
def get_filename(base: str, file_type: FileType) -> str:
return base + file_type # Direct usage!
When to Use Each
Use Enum When:
-
Non-string values needed:
class Status(Enum): PENDING = 1 PROCESSING = 2 COMPLETED = 3 -
Mixed value types:
class Config(Enum): TIMEOUT = 30 HOST = "localhost" PORTS = [8000, 8001] -
Numeric enumerations:
class Priority(Enum): LOW = 1 MEDIUM = 2 HIGH = 3 -
Python < 3.11: StrEnum is not available
Use StrEnum When:
-
String constants only:
class HTTPMethod(StrEnum): GET = "GET" POST = "POST" -
Direct string usage needed:
class Color(StrEnum): RED = "red" BLUE = "blue" # Want to use directly in strings message = f"Color: {Color.RED}" -
JSON serialization:
class Status(StrEnum): ACTIVE = "active" # Works directly with json.dumps json.dumps({"status": Status.ACTIVE}) -
API/Web development: Often need string values
-
Python 3.11+: Available and recommended for string enums
Advanced Usage
Custom StrEnum with Validation
from enum import StrEnum
class Status(StrEnum):
ACTIVE = "active"
INACTIVE = "inactive"
PENDING = "pending"
@classmethod
def _missing_(cls, value):
# Handle case-insensitive lookup
for member in cls:
if member.value.lower() == value.lower():
return member
return None
# Case-insensitive access
status = Status("ACTIVE") # Returns Status.ACTIVE
Enum with Auto Values
from enum import Enum, auto
class Status(Enum):
PENDING = auto()
PROCESSING = auto()
COMPLETED = auto()
# Values are 1, 2, 3
print(Status.PENDING.value) # 1
StrEnum with Auto Values (Python 3.11+)
from enum import StrEnum, auto
class Status(StrEnum):
PENDING = auto()
PROCESSING = auto()
COMPLETED = auto()
# Values are "pending", "processing", "completed"
print(Status.PENDING.value) # "pending"
print(Status.PENDING) # "pending" (direct usage)
Functional API
Enum:
from enum import Enum
Status = Enum('Status', ['ACTIVE', 'INACTIVE'], start=1)
# Status.ACTIVE.value = 1
StrEnum:
from enum import StrEnum
Status = StrEnum('Status', ['ACTIVE', 'INACTIVE'])
# Status.ACTIVE.value = "active"
# Status.ACTIVE = "active" (direct usage)
Migration from Enum to StrEnum
If you have existing Enum code with string values:
Before (Enum):
from enum import Enum
class HTTPMethod(Enum):
GET = "GET"
POST = "POST"
PUT = "PUT"
def make_request(method: HTTPMethod, url: str):
# Need .value everywhere
requests.request(method.value, url)
After (StrEnum):
from enum import StrEnum
class HTTPMethod(StrEnum):
GET = "GET"
POST = "POST"
PUT = "PUT"
def make_request(method: HTTPMethod, url: str):
# Direct usage!
requests.request(method, url)
Common Interview Questions
Q1: What is the main difference between Enum and StrEnum?
Enumcan have values of any type (int, str, tuple, etc.), whileStrEnumonly allows string valuesStrEnummembers can be used directly as strings without needing.value, whileEnumrequires.valuefor string operationsStrEnumis string-compatible for type checking, making it more convenient for string-based operations
Q2: When should you use StrEnum over Enum?
Use StrEnum when:
- All enum values are strings
- You need to use enum members directly in string operations
- You want JSON serialization to work without custom encoders
- You’re working with APIs, web development, or configuration files
- You’re using Python 3.11+
Q3: Can you convert an Enum to StrEnum?
Yes, if all values are strings:
from enum import Enum, StrEnum
# Original Enum
class Status(Enum):
ACTIVE = "active"
INACTIVE = "inactive"
# Convert to StrEnum
class Status(StrEnum):
ACTIVE = "active"
INACTIVE = "inactive"
Q4: What happens if you try to use a non-string value in StrEnum?
You get a TypeError:
from enum import StrEnum
class Status(StrEnum):
ACTIVE = "active"
INACTIVE = 1 # TypeError: values must be strings
Q5: How do Enum and StrEnum handle JSON serialization?
Enum: Requires custom JSON encoder to serialize.valueStrEnum: Works directly withjson.dumps()because it’s string-compatible
Summary
| Aspect | Enum | StrEnum |
|---|---|---|
| Value Types | Any type | Strings only |
| String Usage | Requires .value |
Direct usage |
| Type Compatibility | Generic | String-compatible |
| JSON Serialization | Needs custom encoder | Works directly |
| Python Version | 3.4+ | 3.11+ |
| Use Case | General enumerations | String constants |
Key Takeaway: Use StrEnum when you have string-only enumerations and want the convenience of direct string operations. Use Enum when you need non-string values or are using Python < 3.11.
Interview angle
- “Why use an Enum over string constants?” - a closed, typed set of values that type checkers verify, with real names in tracebacks and no risk of a typo becoming a silent new value.
- “What does
StrEnumadd?” - members are genuinestrinstances (3.11+), so they serialise to JSON and compare to plain strings without.value. That removes the most common friction with enums at API boundaries. - “When would you not use one?” - when the set is genuinely open or defined by external data. An enum that needs updating every time a third party adds a value is a maintenance liability.