Enforcing Unique Values in Python Enums with @unique
Learn how Python enum aliases work, how @unique enforces distinct values, and when allowing aliases is the right design choice.
Python's enum module lets multiple names share the same value, which creates aliases. Aliases are sometimes useful, but when you need each member to have a distinct value, you must enforce that explicitly. The @unique decorator makes the guarantee clear: if any value appears more than once, class creation raises a ValueError instead of silently producing an alias.
The Problem with Duplicate Values in Python Enums
Without a uniqueness constraint, Python permits duplicate values. The first name that binds a value becomes the canonical member; subsequent names become aliases to that same member object. This behavior is intentional, but it can hide bugs. For example, an enum for HTTP status codes that accidentally maps two names to the same numeric value will have fewer members than you expect when you iterate over it.
How Python Enum Handles Duplicates by Default
from enum import Enum class Color(Enum): RED = 1 CRIMSON = 1 print(Color.RED is Color.CRIMSON) # True print(len(Color)) # 1
Iteration returns only canonical members, so CRIMSON does not appear in list(Color). The __members__ dictionary still contains all names, including aliases, but the public iteration protocol filters them out.
Aliases are not always a mistake. They can preserve backward compatibility or give more descriptive names to the same concept, such as SUCCESS and OK referring to the same status code.
Using @unique to Enforce Uniqueness
The @unique decorator checks all values in the enum and raises ValueError if any value is repeated. It runs at class creation time, so the failure happens when the module is imported, not when a member is first used.
from enum import Enum, unique @unique class StatusCode(Enum): OK = 200 CREATED = 201 ACCEPTED = 202 BAD_REQUEST = 400
If you accidentally add a duplicate, the import fails immediately:
from enum import Enum, unique @unique class StatusCode(Enum): OK = 200 CREATED = 201 ACCEPTED = 202 BAD_REQUEST = 400 SUCCESS = 200 # ValueError: duplicate values found in <enum 'StatusCode'>: SUCCESS -> OK
The error message names both the duplicate member and the canonical member it conflicts with, which makes the mistake easy to locate. The check happens once at class definition, so there is no per-member runtime overhead.
When Aliases Are Intentional: Skipping @unique
There are legitimate reasons to allow duplicate values. When mapping an external API's response codes to your own enum, several external codes may map to the same internal meaning. Aliases let you recognize all of those inputs while still treating them as one concept.
from enum import Enum class PaymentStatus(Enum): PENDING = "pending" PROCESSING = "pending" # alias COMPLETED = "completed" FAILED = "failed"
Here, PaymentStatus.PENDING and PaymentStatus.PROCESSING are the same member. Code that checks status is PaymentStatus.PENDING also matches PROCESSING, which can simplify logic when several external states should map to one internal state.
The tradeoff is that len(PaymentStatus) is 3, not 4, and iteration will not show PROCESSING. If you rely on iterating over all names, aliases will be invisible. That is often acceptable when you only need to compare values, but it can cause confusion when the enum is used for documentation or for generating client code.
Runtime Behavior: Lookup and Iteration
When you call PaymentStatus("pending"), Python returns the canonical member, not the alias PROCESSING. Because both names point to the same member, you cannot recover which name was used to create the value from the enum itself.
If you need to see every defined name, including aliases, use EnumClass.__members__.items(). The public iteration protocol (list(EnumClass) or for member in EnumClass) yields only canonical members.
for name, member in PaymentStatus.__members__.items(): print(name, member.value)
This distinction matters when you build serialization layers or generate documentation from an enum. If you want to expose every defined name, iterate over __members__ explicitly.
Custom Uniqueness Validation for Complex Enums
The @unique decorator checks that values are not repeated. If your enum uses tuples or other composite values, equality on the whole value is still checked. But what if uniqueness should be based on a subset of fields? For example, each member has a code and a description, and you only care that the code is unique.
@unique cannot express that condition because it compares the whole value. You can write a custom class decorator instead.
from enum import Enum def unique_code(cls): seen = set() for member in cls.__members__.values(): code = member.value[0] if code in seen: raise ValueError(f"Duplicate code {code} in {cls.__name__}") seen.add(code) return cls @unique_code class ApiError(Enum): NOT_FOUND = (404, "Resource not found") CONFLICT = (409, "Resource conflict") DUPLICATE = (409, "Duplicate resource") # raises ValueError
Using cls.__members__.values() ensures the validator sees aliases as well as canonical members. The check runs at class creation time, just like @unique, so the failure is early and explicit.
Production Considerations for Enum Uniqueness
Enforcing uniqueness is a design decision that affects maintainability and data integrity. In a large codebase, duplicate enum values can cause subtle bugs when you switch on member identity or use enum members as dictionary keys. Because two names with the same value are actually the same object, using both names as dictionary keys will silently overwrite the same entry.
Using @unique is a low-cost safeguard. It runs once at class definition and adds no per-member runtime overhead. The main cost is that you must consciously decide whether aliases are part of your API. If you are building a public library, making the enum unique prevents consumers from relying on aliases that you might later remove. If you need aliases for backward compatibility, document them as aliases rather than separate members.
When incoming data comes from an external source, such as a database or API, uniqueness can be a correctness requirement. If two external codes map to the same enum value, you might silently accept the wrong code. Using @unique forces you to handle that collision before it reaches production.
The table below summarizes when to use @unique versus allowing aliases.
| Scenario | Use @unique | Allow Aliases |
|---|---|---|
| Public API with stable member names | Yes | No |
| Internal mapping of external codes | Often | Yes |
| Enum values used as dictionary keys | Yes | No |
| Backward compatibility for renamed members | No | Yes |
| Iteration must reflect all defined names | Yes | No |
Choosing the right approach depends on whether the enum is a contract or an implementation detail. When it is a contract, uniqueness prevents accidental collisions. When it is an implementation detail, aliases can reduce duplication and simplify logic. The key is to make the decision explicit rather than letting it happen by accident.