Python enumの@uniqueとは?値の重複を防ぐ方法
@uniqueは、Enumの値が重複していないことをクラス定義の時点でチェックするデコレータです。
値をコピペして直し忘れた、といったミスを実行前に止められます。
@uniqueの基本
from enum import Enum, unique
@unique
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
print(list(Color))
実行結果:
[<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 3>]
重複がなければ何も起こりません。Enumはそのまま使えます。
インポートはEnumと並べて書きます。
from enum import Enum, unique
重複するとどうなるか
from enum import Enum, unique
@unique
class NoAlias(Enum):
RED = 1
CRIMSON = 1
実行結果:
ValueError: duplicate values found in <enum 'NoAlias'>: CRIMSON -> RED
CRIMSON -> REDという形で、どれがどれと衝突したかまで表示されます。
重要なのは、これがクラスを定義した瞬間に出ることです。そのメンバーを使う行ではありません。
つまりインポートした時点で落ちるため、ミスがそのまま本番まで流れません。
@uniqueがないと何が起きるか
from enum import Enum
class Status(Enum):
PENDING = 1
RUNNING = 2
DONE = 2 # 3のつもりが2
print("一覧:", list(Status))
print("len :", len(Status))
print("DONE is RUNNING:", Status.DONE is Status.RUNNING)
実行結果:
一覧: [<Status.PENDING: 1>, <Status.RUNNING: 2>]
len : 2
DONE is RUNNING: True
エラーは出ません。Pythonは値が同じメンバーをエイリアス(別名)として扱うためです。
結果として、
Status.DONEはStatus.RUNNINGそのものになる- 一覧に
DONEが出ない len()が3ではなく2になる
「完了にしたのに実行中と判定される」という気づきにくいバグになります。@uniqueは、これを定義時のエラーに変えてくれます。
エイリアスは悪ではない
値の重複が常にミスとは限りません。意図的な別名として使うこともあります。
from enum import Enum
class Color(Enum):
RED = 1
CRIMSON = 1 # 旧名との互換用
GREEN = 2
print(Color.CRIMSON)
print(list(Color))
print(list(Color.__members__))
実行結果:
Color.RED
[<Color.RED: 1>, <Color.GREEN: 2>]
['RED', 'CRIMSON', 'GREEN']
古い名前を残したまま新しい名前に移行する、といった場面で役立ちます。
使い分けはシンプルです。
| 状況 | @unique |
|---|---|
| すべて別々の値にしたい | 付ける |
| 別名を意図的に用意したい | 付けない |
迷ったら付けておくのが安全です。あとから別名が必要になったときに外せば済みます。
エイリアスの詳しい挙動は「Python Enumのメンバーを取得する方法|name・value・反復」で解説しています。
型が違っても重複になる
from enum import Enum, unique
@unique
class T(Enum):
A = 1
B = 1.0
C = True
実行結果:
ValueError: duplicate values found in <enum 'T'>: B -> A, C -> A
Pythonでは1 == 1.0 == Trueが成り立つため、型が違っても同じ値とみなされます。
@uniqueを付けない場合は、3つとも1つのメンバーにまとまります。
class U(Enum):
A = 1
B = 1.0
C = True
print(list(U))
print(U.B is U.A, U.C is U.A)
実行結果:
[<U.A: 1>]
True True
一方、文字列の大文字と小文字は別扱いです。
class V(Enum):
A = 'x'
B = 'X'
print(list(V))
実行結果:
[<V.A: 'x'>, <V.B: 'X'>]
auto()と組み合わせる
from enum import Enum, unique, auto
@unique
class Status(Enum):
PENDING = auto()
RUNNING = auto()
DONE = auto()
print([(m.name, m.value) for m in Status])
実行結果:
[('PENDING', 1), ('RUNNING', 2), ('DONE', 3)]
auto()は値を自動で振るので、そもそも重複しません。
それでも@uniqueを付けておくと、あとから手書きの値を混ぜたときの保険になります。
@unique
class Mixed(Enum):
A = 1
B = auto() # 2になる
C = 2 # Bと衝突
実行結果:
ValueError: duplicate values found in <enum 'Mixed'>: C -> B
auto()の値の決まり方は「Python enumのauto()とは?値を自動で割り当てる方法」で解説しています。
IntEnumやFlagにも使える
from enum import IntEnum, unique
@unique
class Level(IntEnum):
LOW = 1
MIDDLE = 2
HIGH = 3
print(Level.HIGH > Level.LOW)
実行結果:
True
@uniqueはEnumを継承したクラスならどれでも使えます。IntEnumやFlagも対象です。
IntEnumについては「Python IntEnumとは?数値として使えるEnumを解説」を参照してください。
@uniqueのまとめ
from enum import Enum, uniqueでインポート- クラスに付けるだけで値の重複を定義時に検出
- エラーは
ValueError: duplicate values found ... - 付けないと重複はエイリアスとして黙って通る
1・1.0・Trueは同じ値とみなされる- 別名を意図的に使うときだけ外す



