Python Enum Unique: Enforcing Uniqueness with @unique
python enum unique: Learn how to enforce unique values in Python enums using @unique, understand aliases, and decide when uniqueness matters for maintainable code.
Python's enum module allows multiple names to share the same value, which creates aliases. For many use cases, that is fine, but when you need python enum unique members, you must explicitly enforce it. The @unique decorator provides a straightforward way to guarantee that every value appears only once, turning a silent alias into an immediate error at class definition time.
The Problem with Duplicate Values in Python Enums
When you define an enum without any uniqueness constraint, Python permits duplicate values. The first name that binds a value becomes the canonical member; any subsequent names become aliases that reference the same member object. This behavior is intentional and sometimes useful, but it can also hide bugs when you expect every member to have a distinct value. For example, if you define an enum for HTTP status codes and accidentally use the same numeric value for two different names, you may not notice until you iterate over the enum and see fewer members than expected.
The @unique decorator from the enum module enforces that every value appears only once across all members. Applying it to an enum class raises a ValueError at class definition time if any duplicate values are found. This turns a silent alias into an immediate, explicit failure, which is especially valuable in large codebases where an alias might go unnoticed for months.
How Python Enum Handles Duplicates by Default
When you define an enum without @unique, Python creates aliases for duplicate values. The first name that binds a value becomes the canonical member; subsequent names become aliases that reference the same member object. This means that Color.RED and Color.CRIMSON might be the same object if they share the value 1.
from enum import Enum class Color(Enum): RED = 1 CRIMSON = 1 print(Color.RED is Color.CRIMSON) # True print(len(Color)) # 1
Iteration over the enum returns only the canonical members, not the aliases. This can be surprising when you expect each name to appear in list(Color). The __members__ dictionary contains all names, including aliases, but the public iteration protocol filters them out.
Aliases are not always a mistake. They can be useful for backward compatibility or for providing more descriptive names for the same underlying value. For instance, you might want both SUCCESS and OK to refer to the same status code. The default behavior supports that without extra 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 you first use a member.
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 value found in <enum 'StatusCode'>: SUCCESS -> OK
The error message names the duplicate member and the canonical member it conflicts with. This makes the mistake easy to locate and fix. The decorator adds no runtime overhead because the check happens only once at class definition.
When Aliases Are Intentional: Skipping @unique
There are legitimate reasons to allow duplicate values. For example, when you are mapping an external API's response codes to your own enum, multiple external codes might map to the same internal meaning. In that case, aliases give you a way to recognize both 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 will also match PROCESSING. This can simplify logic when you want to treat several external states as a single internal state.
The tradeoff is that len(PaymentStatus) will be 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 if you use the enum for documentation or for generating client code.
Runtime Behavior: How Duplicates Affect Lookup and Iteration
The way Python resolves enum members has a direct effect on how you should write code that depends on uniqueness. When you access a member by value, Python returns the canonical member, not an alias. So PaymentStatus("pending") returns PaymentStatus.PENDING, even if you defined PROCESSING later. This means that if you want to know which name was used to create a member, you cannot recover that information from the enum itself.
Iteration uses the __members__ ordered dictionary, but it filters out aliases. The __members__ mapping contains all names, including aliases, but the public iteration protocol (list(EnumClass), for member in EnumClass) only yields canonical members. If you need to see every name, you must use EnumClass.__members__.items().
for name, member in PaymentStatus.__members__.items(): print(name, member.value)
This distinction matters when you are building serialization layers or generating documentation from an enum. If you want to expose every defined name, you need to iterate over __members__ explicitly.
Custom Uniqueness Validation for Complex Enums
The @unique decorator only checks that values are not repeated. If your enum uses tuples or other composite values, the check still works because equality is based on the value's equality. But what if you need uniqueness based on a subset of fields? For example, you might have an enum where each member has a code and a description, and you only care that the code is unique, not the entire tuple.
@unique will not help there because it compares the whole value. You can write a custom class decorator that inspects the members and raises an error if a specific attribute is duplicated.
from enum import Enum def unique_code(cls): seen = set() for member in cls: 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
This approach gives you control over what uniqueness means for your domain. It 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, an enum with duplicate values can cause subtle bugs when you switch on member identity or use the enum as a dictionary key. Two names that share a value will hash identically, so they will overwrite each other in a dictionary if you use them as keys. That can lead to data loss without any error.
Using @unique is a low-cost safeguard. It adds no runtime overhead because the check happens only at class definition time. The only 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, you can still provide them, but you should document that they are aliases and not separate members.
When you are working with data that comes from an external source, such as a database or an API, you might want to validate that the incoming values map to exactly one enum member. In that case, uniqueness is not just a code quality issue; it is 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 decide how 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.