[解決!Python]Pythonで列挙型(enum)を使うには解決!Python

enumモジュールのEnumクラスやIntEnumクラス、Flagクラスを継承して、列挙型を定義したり、そのメンバーを扱ったりする方法を紹介する。

» 2024年02月06日 05時00分 公開
[かわさきしんじDeep Insider編集部]
「解決!Python」のインデックス

連載目次

本記事は2022年6月14日に公開されたものをPython 3.12.1で動作確認し、Python 3.11以降でのenumモジュールの変更点について記述を追加したものです(改訂日:2024年2月6日)。


from enum import Enum

# 列挙型の定義
class Animal(Enum):
    CAT = 1
    DOG = 2
    COW = 3

# 列挙型のメンバーへのアクセス
print(Animal.CAT)  # Animal.CAT
repr(Animal.CAT)  # '<Animal.CAT: 1>'
print(Animal.DOG.name)  # DOG
print(Animal.COW.value)  # 3

# 列挙型のメンバーの比較
favorit_animal = 1
if favorit_animal == Animal.CAT:  # NG:列挙型のメンバーと整数値は比較できない
    print('meow')
else:
    print('???')
# 出力結果: ???

favorit_animal = 1
favorit_animal = Animal(favorit_animal)  # 指定した値に対応するメンバーを取得
print(favorit_animal)  # Animal.CAT
if favorit_animal == Animal.CAT:  # OK:Animal.CAT == Animal.CAT
    print('meow')
else:
    print('???')
# 出力結果: meow

favorit_animal = 'DOG'
favorit_animal = Animal[favorit_animal]  # 列挙型のメンバーにその名前でアクセス
print(favorit_animal)  # Animal.DOG
if favorit_animal == Animal.DOG:  # OK
    print('bow wow')
else:
    print('???')
# 出力結果: bow wow

# 列挙型のメンバー間に順序関係はない
if Animal.CAT < Animal.DOG:
    print('cat has a higher priority than dog')
# TypeError: '<' not supported between instances of 'Animal' and 'Animal'

# 列挙型のメンバーの列挙
for member in Animal:
    print(member)
# 出力結果:
#Animal.CAT
#Animal.DOG
#Animal.COW

# __members__属性を使ったメンバーの列挙
for name, animal in Animal.__members__.items():
    print(f'name: {name}, member: {animal}')
# 出力結果:
#name: CAT, member: Animal.CAT
#name: DOG, member: Animal.DOG
#name: COW, member: Animal.COW

# 列挙型のメンバーの値を自動的に設定する
from enum import auto

class Animal(Enum):
    CAT = auto()  # デフォルトでは1始まりの値となる
    DOG = auto()
    COW = auto()

for member in Animal:
    repr(member)
# 出力結果:
#'<Animal.CAT: 1>'
#'<Animal.DOG: 2>'
#'<Animal.COW: 3>'

# メンバーに別名を与える
class Animal(Enum):
    CAT = auto()
    DOG = auto()
    COW = auto()
    NYANKO = CAT  # Animal.NYANKOはAnimal.CATの別名

print(Animal.NYANKO)  # Animal.CAT

# メンバーが重複する値を持つことを許さない
from enum import unique

@unique
class Animal(Enum):
    CAT = auto()  # デフォルトでは1始まりの値となる
    DOG = auto()
    COW = auto()
    NYANKO = CAT
# ValueError: duplicate values found in <enum 'Animal'>: NYANKO -> CAT

# 他の列挙型との比較もできない
class PhoneticCode(Enum):
    ALPHA = auto()
    BETA = auto()
    GAMMA = auto()

print(f'{PhoneticCode.ALPHA.value}, {Animal.CAT.value}'# 1, 1
print(PhoneticCode.ALPHA == Animal.CAT)  # False

# 整数値との比較が可能な列挙型
from enum import IntEnum

class PhoneticCode(IntEnum):
    ALPHA = auto()
    BETA = auto()
    GAMMA = auto()

print(PhoneticCode.ALPHA == 1# True
print(PhoneticCode.ALPHA == Animal.CAT)  # False

# Python 3.11で追加されたStrEnumクラスの使用例
from enum import StrEnum

class Command(StrEnum):
    NORTH = auto()  # 'north'
    SOUTH = auto()  # 'south'
    EAST = auto()  # 'east'
    WEST = auto()  # 'west'

cmd = input('command(north/south/east/west): ')

if cmd == Command.NORTH:
    ...

# フラグ
from enum import Flag

class Color(Flag):
    BLUE = auto()  # 1 = 0b001
    GREEN = auto()  # 2 = 0b010
    RED = auto()  # 4 = 0b100
    CYAN = GREEN | BLUE  # 0b010 | 0b001 = 0b11 = 3
    MAGENTA = RED | BLUE  # 0b100 | 0b001 = 0b101 = 5
    YELLOW = RED | GREEN  # 0b100 | 0b010 = 0b110 = 6
    WHITE = RED | GREEN | BLUE  # 0b100 | 0b010 | 0b001 = 0b111 = 7
    BLACK = RED & GREEN & BLUE  # 0b100 & 0b010 & 0b001 = 0b000 = 0
    AQUA = CYAN
    FUCHSIA = MAGENTA

for c in Color:
    repr(c)
# 出力結果はPythonのバージョンによって異なる(後述)

# 別名を含む全てのメンバーを列挙する
for n, c in Color.__members__.items():
    print(f'{n}: {c.value}')
# 出力結果:
#BLUE: 1
#GREEN: 2
#RED: 4
#CYAN: 3
#MAGENTA: 5
#YELLOW: 6
#WHITE: 7
#BLACK: 0
#AQUA: 3
#FUCHSIA: 5

# 列挙型にメソッドを持たせる
class Color(Flag):
    BLUE = auto()
    GREEN = auto()
    RED = auto()
    CYAN = GREEN | BLUE
    MAGENTA = RED | BLUE
    YELLOW = RED | GREEN
    WHITE = RED | GREEN | BLUE
    BLACK = RED & GREEN & BLUE
    AQUA = CYAN
    FUCHSIA = MAGENTA

    def __repr__(self):
        return f'<Color.{self.name}: {self.value:#05b}>'

for c in Color.__members__.values():
    repr(c)
# 出力結果:
#'<Color.BLUE: 0b001>'
#'<Color.GREEN: 0b010>'
#'<Color.RED: 0b100>'
#'<Color.CYAN: 0b011>'
#'<Color.MAGENTA: 0b101>'
#'<Color.YELLOW: 0b110>'
#'<Color.WHITE: 0b111>'
#'<Color.BLACK: 0b000>'
#'<Color.CYAN: 0b011>'
#'<Color.MAGENTA: 0b101>'


列挙型の基本

 Pythonでは言語標準で列挙型(定数として機能する値に名前を付けたものの集まり)を定義することはできない。しかし、Pythonに標準で付属するenumモジュールを使用することで列挙型を定義/使用できるようになる。

 簡単な例を以下に示す。

from enum import Enum

class Animal(Enum):
    CAT = 1
    DOG = 2
    COW = 3


 これはAnimalという列挙型を定義する例だ。enumモジュールのEnumクラスを基底クラスとしてAnimalクラスを定義している。列挙型のメンバーとしてはCAT、DOG、COWの3つがあり、それぞれ1、2、3という値が与えられている(本稿では整数値を列挙型メンバーの値として割り当てているが、実際には任意のオブジェクトを割り当てられる)。

 列挙型のメンバーには次のように「列挙型.メンバー名」としてアクセスできる。

print(Animal.CAT)  # Animal.CAT
repr(Animal.CAT)  # '<Animal.CAT: 1>'


 上の出力結果から、Animal列挙型のCATメンバーの値が1であることが分かるはずだ。

 列挙型のメンバーにはname属性とvalue属性があり、これらを使うことでメンバー名と割り当てられた値を取得できる。

print(Animal.DOG.name)  # DOG
print(Animal.COW.value)  # 3


列挙型のメンバーの比較

 列挙型のメンバーはif文やmatch文で何かの値と比較して、メンバーのどれが指定されているかによって、処理を振り分けるといった処理をするために使うことが多いはずだ。

 以下に例を示す。

favorit_animal = 1
if favorit_animal == Animal.CAT:  # NG:列挙型のメンバーと整数値は比較できない
    print('meow')
else:
    print('???')
# 出力結果: ???


 しかし、上の例では整数値1とAnimcal.CAT(その値は1)との比較が失敗して「???」と表示される。このようにEnumクラスから派生した列挙型は整数値と比較できないようになっている。そこで、列挙型以外の値と列挙型のメンバーとを比較する際には、比較対象の値(例えば、input関数の戻り値を整数化した値など)に対応する列挙型の値を取得する必要がある。

 以下に例を示す。

favorit_animal = 1
favorit_animal = Animal(favorit_animal)  # 指定した値に対応するメンバーを取得
print(favorit_animal)  # Animal.CAT
if favorit_animal == Animal.CAT:  # OK:Animal.CAT == Animal.CAT
    print('meow')
else:
    print('???')
# 出力結果: meow


 この例では「Animal(favorit_animal)」として、変数favorit_animalの値(ここでは1)に対応したメンバー(Animal.CAT)を取得し、それを用いて、比較を行うようにしている。この結果、猫の鳴き声である「meow」が表示されるようになった。

 あるいは、列挙型のメンバーの名前を使って以下のようにインデックスアクセスを行い、対応するメンバーを取得することも可能だ。

favorit_animal = 'DOG'
favorit_animal = Animal[favorit_animal]  # 列挙型のメンバーにその名前でアクセス
print(favorit_animal)  # Animal.DOG
if favorit_animal == Animal.DOG:  # OK
    print('bow wow')
else:
    print('???')
# 出力結果: bow wow


 この例では「Animal[favorit_animal]」として、変数favorit_animalの値(ここでは'DOG')に対応したメンバー(Animal.DOG)を取得し、それを用いて、比較を行うようにしている。この結果、犬の鳴き声である「bow wow」が表示されるようになった。

 なお、列挙型のメンバーの間には順序関係はないので、次のようなコードは書けない。

# 列挙型のメンバー間に順序関係はない
if Animal.CAT < Animal.DOG:
    print('cat has a higher priority than dog')
# TypeError: '<' not supported between instances of 'Animal' and 'Animal'


列挙型のメンバーの列挙

 列挙型のメンバーは次のようにして反復的に取り出せる。取り出される順序は、メンバーを定義した順序となる。

for member in Animal:
    print(member)
# 出力結果:
#Animal.CAT
#Animal.DOG
#Animal.COW


 列挙型の__members__属性にはメンバーの名前と対応するメンバーがマッピングの形式で含まれているので、この属性を使ってもよい。以下は__members__属性のitemsメソッドにより、メンバーの名前と値を列挙する例だ。

for name, animal in Animal.__members__.items():
    print(f'name: {name}, member: {animal}')
# 出力結果:
#name: CAT, member: Animal.CAT
#name: DOG, member: Animal.DOG
#name: COW, member: Animal.COW


列挙型のメンバーの値を自動的に設定する

 冒頭のコードでは「メンバー名 = 値」としていたが、メンバーが特定の値である必要がなければ、enumモジュールのautoクラスを用いてメンバーに割り当てる値を自動生成してもよい。デフォルトでは1から始まる整数値が順次割り当てられる。

 以下に例を示す。

from enum import auto

class Animal(Enum):
    CAT = auto()  # デフォルトでは1始まりの値となる
    DOG = auto()
    COW = auto()

for member in Animal:
    repr(member)
# 出力結果:
#'<Animal.CAT: 1>'
#'<Animal.DOG: 2>'
#'<Animal.COW: 3>'


 この例では3つのメンバーを定義するのにautoクラスを使っている。これにより、各メンバーにどんな値が割り当てられたかをfor文で調べているが、順番に1、2、3が割り当てられていることが分かる。

メンバーに別名を与える

 列挙型では、同じ値を持つメンバーを複数定義できる。この場合、2つ目以降のメンバーは1つ目のメンバーの別名(エイリアス)となる。これには以下のように、別名となるメンバーが既に定義済みのメンバーの値と同じ値を持つようにすればよい。

class Animal(Enum):
    CAT = auto()
    DOG = auto()
    COW = auto()
    NYANKO = CAT  # Animal.NYANKOはAnimal.CATの別名

print(Animal.NYANKO)  # Animal.CAT


 この例では「NYANKO = CAT」としているので、Animal.NYANKOはAnimal.CATの別名となる。print関数呼び出しでは「print(Animal.NYANKO)」としているが、これにより得られるのが「Animal.CAT」となっていることに注目されたい。別名を参照すると、元のメンバーが得られている。

メンバーが重複する値を持つことを許さない

 上では別名を持つ列挙型のメンバーを定義できることを示したが、逆に全てのメンバーがそれぞれ異なる値を持つことを保証したいこともある。このときには、enumモジュールのuniqueデコレーターを使用する。

 以下に例を示す。

from enum import unique

@unique
class Animal(Enum):
    CAT = auto()
    DOG = auto()
    COW = auto()
    NYANKO = CAT
# ValueError: duplicate values found in <enum 'Animal'>: NYANKO -> CAT


 このコードを実行すると、例外が発生して、最後のコメントにあるように列挙型Animalに重複する値があることがレポートされる。列挙型のメンバーが重複する値を持たないことが重要であるときにはuniqueデコレーターを使うのがよいだろう。

整数値との比較が可能な列挙型

 先ほどはEnumを継承する列挙型は整数値との比較ができないことを示した。このことは整数値だけに限らず、Enumを継承する列挙型同士でのメンバーの比較も行えない。

 以下に例を示す。

class PhoneticCode(Enum):
    ALPHA = auto()
    BETA = auto()
    GAMMA = auto()

print(f'{PhoneticCode.ALPHA.value}, {Animal.CAT.value}'# 1, 1
print(PhoneticCode.ALPHA == Animal.CAT)  # False


 この例では、列挙型PhoneticCodeを定義している。そして、PhoneticCode.ALPHAとAnimal.CATに割り当てられた値は共に1である。だが、「PhoneticCode.ALPHA == Animal.CAT」という比較の結果がFalseとなることに注目されたい。

 整数値との比較が重要になる場合、enumモジュールのIntEnumクラスが使える。

from enum import IntEnum

class PhoneticCode(IntEnum):
    ALPHA = auto()
    BETA = auto()
    GAMMA = auto()

print(PhoneticCode.ALPHA == 1# True
print(PhoneticCode.ALPHA == Animal.CAT)  # False


 この例では列挙型PhoneticCodeがIntEnumクラスを継承するように定義している。1つ目のprint文の出力結果を見れば分かる通り、「PhoneticCode.ALPHA == 1」が成立している。つまり、整数値と列挙型PhoneticCodeのメンバーとの比較が(ここでは思った通りに)行えている。ただし、2つ目の出力結果を見ると分かるが、IntEnumを継承した列挙型とEnumを継承した列挙型ではやはり同じ値を持つメンバーであっても等値だとは見なされない点には注意しよう。コードは示さないが、列挙型AnimalがIntEnumを継承するようにすれば2つ目の比較もTrueが返されるようになる。

 なお、Python 3.11以降では、そのメンバーを文字列として扱えるStrEnumクラスなども追加されている。以下に例を示す。StrEnumの派生クラスでauto()を呼び出した場合、その値はメンバー名を小文字化した文字列となる。

from enum import StrEnum

class Command(StrEnum):
    NORTH = auto()  # 'north'
    SOUTH = auto()  # 'south'
    EAST = auto()  # 'east'
    WEST = auto()  # 'west'

cmd = input('command(north/south/east/west): ')

if cmd == Command.NORTH:
    ...


 この他にも、Python 3.11以降ではenumモジュールにさまざまな機能が追加されている。詳しくはPythonのドキュメント「列挙型 HOWTO」などを参照されたい。

フラグ

 関連のある値を、2進数値の各ビットに割り当てて、そのビットが0か1かで処理を分岐するということもよくある。ここまでに見てきたEnumクラスを継承する列挙型では、どんな値を割り当てるかを明示的に指定することでそのような使い方もできるだろうが、Flagクラス(とenum.autoクラス)を使うとそうした処理を自動化する。

 以下に例を示す。

from enum import Flag

class Color(Flag):
    BLUE = auto()  # 1 = 0b001
    GREEN = auto()  # 2 = 0b010
    RED = auto()  # 4 = 0b100
    CYAN = GREEN | BLUE  # 0b010 | 0b001 = 0b11 = 3
    MAGENTA = RED | BLUE  # 0b100 | 0b001 = 0b101 = 5
    YELLOW = RED | GREEN  # 0b100 | 0b010 = 0b110 = 6
    WHITE = RED | GREEN | BLUE  # 0b100 | 0b010 | 0b001 = 0b111 = 7
    BLACK = RED & GREEN & BLUE  # 0b100 & 0b010 & 0b001 = 0b000 = 0
    AQUA = CYAN
    FUCHSIA = MAGENTA


 この例ではRGBを各1ビットの値として表現して、8色を表現する列挙型を定義している。autoクラスを使っているので、青/緑/赤を表す3つのメンバーの値は自動的に割り当てられる。最初に定義しているBLUEの値は1(0b001)となり、次に定義しているGREENの値は2(0b010)となり、REDは4(0b100)となる。フラグのメンバーを定義する場合には下位ビットから順に記述する必要がある点には注意しよう。

 その後のメンバーについてはビット演算を行って、その値を決定している。WHITEならRGBの全てのフラグが1であり(0b111=7)、BLACKなら全てのフラグが0である(0b000=0)。

 最後にはCYANとMAGENTAの別名であるAQUAとFUCHSIAも定義している。以下は各メンバーの値を調べるコードだ。ただし、Python 3.10までとPython 3.11以降では出力結果が異なる点には注意されたい。以下はPython 3.10までの出力結果だ。

import sys
vinfo = sys.version_info
print(f'version: {vinfo.major}.{vinfo.minor}'# version: 3.10

for c in Color:
    repr(c)
# 出力結果:
#'<Color.BLUE: 1>'
#'<Color.GREEN: 2>'
#'<Color.RED: 4>'
#'<Color.CYAN: 3>'
#'<Color.MAGENTA: 5>'
#'<Color.YELLOW: 6>'
#'<Color.WHITE: 7>'
#'<Color.BLACK: 0>'


 気が付くのは別名であるColor.AQUAとColor.FUCHSIAについては列挙されていない点だ。enumクラスでは別名は列挙(イテレート)の対象とはならないのがその理由である。一方、Python 3.11以降の出力結果は次のようになる(以下はPython 3.12を使用したもの)。

import sys
vinfo = sys.version_info
print(f'version: {vinfo.major}.{vinfo.minor}'# version: 3.12

for c in Color:
    repr(c)
# 出力結果:
#'<Color.BLUE: 1>'
#'<Color.GREEN: 2>'
#'<Color.RED: 4>'


 こちらでは2のべき乗の値(1つのビットだけが1になっている値)となるメンバーだけが列挙されるようになっている。Python 3.11以降で独立したドキュメントとして用意されている「列挙型 HOWTO」には「Aliases for flags include values with multiple flags set, such as 3, and no flags set, i.e. 0.」という記述がある。意訳すると「フラグの別名には、3のような複数フラグの組み合わせや、フラグが立っていないもの(つまり、0)も含まれる」となる。つまり、Color.RED、Color.GREEN、Color.BLUEの組み合わせで得られる色を示すメンバーや、3つの原色が1つも含まれないColor.BLACKはPython 3.11以降では別名として扱われるということだろう。

 Color.AQUAやColor.FUCHSIAを含む別名を列挙するには、既に述べたが__members__属性を使用する。以下の例ではitemsメソッドを使って、メンバー名とその値を取得している。

for n, c in Color.__members__.items():
    print(f'{n}: {c.value}')
# 出力結果:
#BLUE: 1
#GREEN: 2
#RED: 4
#CYAN: 3
#MAGENTA: 5
#YELLOW: 6
#WHITE: 7
#BLACK: 0
#AQUA: 3
#FUCHSIA: 5


 なお、Enumを継承する列挙型と同様に、Flagを継承する列挙型も自身の列挙型のメンバー同士での比較は可能だが、整数値や他の列挙型との比較はできないことには注意されたい。enumモジュールには、これをサポートするIntFlagクラスもあるが、これについては説明を割愛する(Pythonのドキュメント「列挙型 HOWTO」ではEnumクラスとFlagクラスの使用が推奨されていて、これらではうまく処理できないときにIntEnumクラスやIntFlagクラスを使うことを検討するように書かれている)。

列挙型にメソッドを持たせる

 最後に列挙型にメソッドを持たせる例を示す。

class Color(Flag):
    BLUE = auto()
    GREEN = auto()
    RED = auto()
    CYAN = GREEN | BLUE
    MAGENTA = RED | BLUE
    YELLOW = RED | GREEN
    WHITE = RED | GREEN | BLUE
    BLACK = RED & GREEN & BLUE
    AQUA = CYAN
    FUCHSIA = MAGENTA

    def __repr__(self):
        return f'<Color.{self.name}: {self.value:#05b}>'

for c in Color.__members__.values():
    repr(c)
# 出力結果:
#'<Color.BLUE: 0b001>'
#'<Color.GREEN: 0b010>'
#'<Color.RED: 0b100>'
#'<Color.CYAN: 0b011>'
#'<Color.MAGENTA: 0b101>'
#'<Color.YELLOW: 0b110>'
#'<Color.WHITE: 0b111>'
#'<Color.BLACK: 0b000>'
#'<Color.CYAN: 0b011>'
#'<Color.MAGENTA: 0b101>'


 このコードでは、__repr__メソッドを定義して、列挙型の各メンバーをrepr関数で文字列化する際に2進数値として表現するようにしている。

 このように列挙型にはメソッドを持たせて、メンバーの値を使った何らかの処理を行うことも可能だ。

「解決!Python」のインデックス

解決!Python

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。