きり丸の技術日記

技術検証したり、資格等をここに残していきます。

PythonでEnumをfor文で回したら値をすべて出力しなかった

パラメータの値を元に、PythonのEnumで定義した値を取得するメソッドを作ろうとしました。しかし、PythonのEnumの知識が弱かったため、事前に定義していたEnumをすべて取得できない事象が発生しました。

この記事では、PythonのEnumの仕様を確認して、Enumで定義した値をすべて取得できなかった原因をメモします。

環境

  • Python
    • 3.8.6

for文でEnumの値が取得できなかった原因

Enumに定義していた値(_value_)が重複していたため。

まとめ

  1. 名前(name)の重複は許さない
  2. 値(value)の重複は許す。しかし、実行時に名前ごと欠落する
  3. Enumをタプルで定義している場合に、_value_を上書きすると怪しい挙動になる
  4. 値が重複した状態でも、全部の名前を出力する方法はある。しかし、期待した値を取得できないことがある

動作確認

名前の重複は許さない

名前が重複している場合、実行時にエラーが発生します。

from enum import Enum

class Color1(Enum):
    RED = 1 
    RED = 2 

# TypeError: Attempted to reuse key: 'RED

値の重複は許すが、実行時に欠落する

名前と違って値は重複しても、実行時エラーは発生しません。しかし、値が重複していると名前ごと欠落します。

from enum import Enum

class Color1(Enum):
    RED = 1 
    BLACK = 2
    WHITE = 2
def test_foo():
    for element in Color1:
        print(element)
# 出力される値
# Color1.RED
# Color1.BLACK
# 出力されない
# Color1.WHITE

Enumをタプルで持たせたケース

ちなみに、私がハマったのは亜種です。Enumをタプルで持たせていたのですが、値を上書きしていたために目的の名前を取得できませんでした。_value_を上書きしない場合は、タプルのまま_valueにセットされますので問題は発生しません。

class Color2(Enum):
    RED = (1, 1)
    BLACK = (2, 2)
    WHITE = (3, 2)

    def __init__(self, id, value):
        self.id = id      
        # この1行で_value_を上書きすると、値重複が発生する
        self._value_ = value 

値が重複していても、名前を取得する

値が重複していた状態でも、名前を取得する方法はあります。定義したEnumに対して__members__.items():を使用することで、すべての名前を取得できます。ただし、注意してください。名前は取得できますが、は取得できない可能性があります。

先ほどのケースであるタプルでEnumを指定しており、_value_を上書きしているケースの場合、期待しない挙動をします。次の例では、WHITE = (3, 2)と定義しているので、取得したい値は(3, 2)ですが、_value_を上書きしているためにBLACKである(2, 2)を取得してしまいます。

class Color2(Enum):
    RED = (1, 1)
    BLACK = (2, 2)
    WHITE = (3, 2)

    def __init__(self, id, value):
        self.id = id      
        self._value_ = value 
def test_bar():
    for name, element in Color2.__members__.items():  
        print(name)
        print(element)

# 出力される値
# RED
# Color2.RED
# BLACK
# Color2.BLACK
# WHITE
# Color2.BLACK

# WHITEの下のColor2.BLACKはtypoではありません。

値の重複を許容しないようにする

Enum標準ライブラリのuniqueアノテーションを使用すると、値が重複していた際にも実行時エラーを発生させられます。

from enum import Enum, unique

@unique
class Color3(Enum):
    RED = (1, 1)
    BLACK = (2, 2)
    WHITE = (3, 2)

    def __init__(self, id, value):
        self.id = id      
        self._value_ = value

# E   ValueError: duplicate values found in <enum 'Color3'>: WHITE -> BLACK

ソースコード

終わりに

Pythonはメインで使用していない言語ですので、たまにハマりますね。

私には@uniqueで値を重複を許容しないようにするのか、__members__.items():を使用して値の重複を許容するのか、どちらがよい設計かは判断できません。@uniqueを使いつつ、タプルで定義して一意なIDを持たせておく方法が良さそうな予感はしていますが。

どちらにしてもPythonらしくない面倒な書き方だと思うので、もっと直感的に書けるようにしてほしいです。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

参考情報

f:id:nainaistar:20211120115830p:plain