8.13. enum
— 列挙型のサポート¶
バージョン 3.4 で追加.
ソースコード: Lib/enum.py
列挙型は、一意の定数値に束縛された識別名 (メンバー) の集合です。列挙型の中でメンバーの同一性を比較でき、列挙型自身でイテレートが可能です。
8.13.1. モジュールコンテンツ¶
このモジュールでは一意の名前と値の集合を定義するために使用する Enum
と IntEnum
、およびデコレーターを定義する unique()
を提供しています。
-
enum.
unique
()¶ 一つの名前だけがひとつの値に束縛されていることを保証する Enum クラスのデコレーターです。
8.13.2. Enum の作成¶
列挙型は読み書きが容易になるよう class
文を使って作成します。もうひとつの作成方法は 機能 API で説明しています。列挙型は以下のように Enum
のサブクラスとして定義します:
>>> from enum import Enum
>>> class Color(Enum):
... red = 1
... green = 2
... blue = 3
...
注釈
用語
クラス
Color
は 列挙型 (または Enum) です属性
Color.red
、Color.green
、その他は 列挙型のメンバー (または Enum メンバー) です。列挙型のメンバーは 名前 と 値 を持ちます (
Color.red
の名前はred
、Color.blue
の値は3
など)
注釈
Enum の作成に class
文を使用するものの、Enum は通常の Python クラスではありません。詳細は Enum はどう違うのか? を参照してください。
列挙型のメンバーは人が読める文字列表現を持ちます:
>>> print(Color.red)
Color.red
...その一方でそれらの repr
はより多くの情報を持っています:
>>> print(repr(Color.red))
<Color.red: 1>
列挙型メンバーの データ型 はそれが所属する列挙型になります:
>>> type(Color.red)
<enum 'Color'>
>>> isinstance(Color.green, Color)
True
>>>
Enum メンバーは自身の名前を持つだけのプロパティも持っています:
>>> print(Color.red.name)
red
列挙型は定義順でのイテレーションをサポートしています:
>>> class Shake(Enum):
... vanilla = 7
... chocolate = 4
... cookies = 9
... mint = 3
...
>>> for shake in Shake:
... print(shake)
...
Shake.vanilla
Shake.chocolate
Shake.cookies
Shake.mint
列挙型のメンバーはハッシュ化可能なため、辞書や集合で使用できます:
>>> apples = {}
>>> apples[Color.red] = 'red delicious'
>>> apples[Color.green] = 'granny smith'
>>> apples == {Color.red: 'red delicious', Color.green: 'granny smith'}
True
8.13.3. 列挙型メンバーおよびそれらの属性へのプログラム的アクセス¶
プログラム的にメンバーに番号でアクセスしたほうが便利な場合があります (例えばプログラムを書いている時点で正確な色がまだわからなく、Color.red
と書くのが無理な場合など)。Enum
はそのようなアクセスも許容します:
>>> Color(1)
<Color.red: 1>
>>> Color(3)
<Color.blue: 3>
列挙型メンバーに 名前 でアクセスしたい場合はアイテムとしてアクセスできます:
>>> Color['red']
<Color.red: 1>
>>> Color['green']
<Color.green: 2>
列挙型メンバーの name
か value
が必要な場合:
>>> member = Color.red
>>> member.name
'red'
>>> member.value
1
8.13.4. 列挙型メンバーと値の重複¶
同じ名前の列挙型メンバーを複数持つことはできません:
>>> class Shape(Enum):
... square = 2
... square = 3
...
Traceback (most recent call last):
...
TypeError: Attempted to reuse key: 'square'
ただし、複数の列挙型メンバーが同じ値を持つことはできます。同じ値を持つ 2 つのメンバー A および B (先に定義したのは A) が与えられたとき、B は A の別名になります。A および B を値で調べたとき、A が返されます。B を名前で調べたとき、A が返されます:
>>> class Shape(Enum):
... square = 2
... diamond = 1
... circle = 3
... alias_for_square = 2
...
>>> Shape.square
<Shape.square: 2>
>>> Shape.alias_for_square
<Shape.square: 2>
>>> Shape(2)
<Shape.square: 2>
注釈
すでに定義されている属性と同じ名前のメンバー (一方がメンバーでもう一方がメソッド、など) の作成、あるいはメンバーと同じ名前の属性の作成はできません。
8.13.5. 番号付けの値が同一であることの確認¶
デフォルトでは、前述のように複数の名前への同じ値の定義は別名とすることで許されています。この挙動を望まない場合、以下のデコレーターを使用することで各値が列挙型内で一意かどうか確認できます:
-
@
enum.
unique
列挙型専用の class
デコレーターです。列挙型の __members__
に別名がないかどうか検索します; 見つかった場合、ValueError
が詳細情報とともに送出されます:
>>> from enum import Enum, unique
>>> @unique
... class Mistake(Enum):
... one = 1
... two = 2
... three = 3
... four = 3
...
Traceback (most recent call last):
...
ValueError: duplicate values found in <enum 'Mistake'>: four -> three
8.13.6. イテレーション¶
列挙型のメンバーのイテレートは別名をサポートしていません:
>>> list(Shape)
[<Shape.square: 2>, <Shape.diamond: 1>, <Shape.circle: 3>]
特殊属性 __members__
は名前をメンバーにマッピングする順序を記憶する辞書です。列挙型内で定義されたすべての名前を別名も含めて格納しています:
>>> for name, member in Shape.__members__.items():
... name, member
...
('square', <Shape.square: 2>)
('diamond', <Shape.diamond: 1>)
('circle', <Shape.circle: 3>)
('alias_for_square', <Shape.square: 2>)
属性 __members__
は列挙型メンバーへの詳細なアクセスに使用できます。以下はすべての別名を探す例です:
>>> [name for name, member in Shape.__members__.items() if member.name != name]
['alias_for_square']
8.13.7. 比較¶
列挙型メンバーは同一性を比較できます:
>>> Color.red is Color.red
True
>>> Color.red is Color.blue
False
>>> Color.red is not Color.blue
True
列挙型の値の順序の比較はサポートされて いません。Enum メンバーは整数ではありません (IntEnum を参照してください):
>>> Color.red < Color.blue
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: Color() < Color()
ただし等価の比較は定義されています:
>>> Color.blue == Color.red
False
>>> Color.blue != Color.red
True
>>> Color.blue == Color.blue
True
非列挙型の値との比較は常に不等となります (繰り返しになりますが、IntEnum
はこれと異なる挙動になるよう設計されています):
>>> Color.blue == 2
False
8.13.8. 列挙型で許されるメンバーと属性¶
上述の例では列挙型の値に整数を使用しています。整数の使用は短くて使いやすい (そして 機能 API でデフォルトで提供されています) のですが、厳密には強制ではありません。ほとんどの事例では列挙型の実際の値が何かを気にしていません。しかし、値が重要で ある 場合、列挙型は任意の値を持つことができます。
列挙型は Python のクラスであり、通常どおりメソッドや特殊メソッドを持つことができます:
>>> class Mood(Enum):
... funky = 1
... happy = 3
...
... def describe(self):
... # self is the member here
... return self.name, self.value
...
... def __str__(self):
... return 'my custom str! {0}'.format(self.value)
...
... @classmethod
... def favorite_mood(cls):
... # cls here is the enumeration
... return cls.happy
...
上記の結果が以下のようになります:
>>> Mood.favorite_mood()
<Mood.happy: 3>
>>> Mood.happy.describe()
('happy', 3)
>>> str(Mood.funky)
'my custom str! 1'
何が許されているかのルールは次のとおりです。先頭と末尾が 1 個のアンダースコアの名前は列挙型により予約されているため、使用できません。列挙型内で定義されたその他すべての名前は、その列挙型のメンバーとして使用できます。特殊メソッド (__str__()
, __add__()
, など) と、メソッドを含むデスクリプタ (記述子) は例外です。
注意: 列挙型で __new__()
および/または __init__()
を定義した場合、列挙型メンバーに与えられた値はすべてこれらのメソッドに渡されます。例 Planet を参照してください。
8.13.9. 列挙型のサブクラス化の制限事項¶
列挙型のサブクラスの作成はその列挙型にメンバーが一つも定義されていない場合のみ行なえます。従って以下は許されません:
>>> class MoreColor(Color):
... pink = 17
...
Traceback (most recent call last):
...
TypeError: Cannot extend enumerations
以下のような場合は許されます:
>>> class Foo(Enum):
... def some_behavior(self):
... pass
...
>>> class Bar(Foo):
... happy = 1
... sad = 2
...
メンバーが定義された列挙型のサブクラス化を許可すると、いくつかのデータ型およびインスタンスの重要な不変条件の違反を引き起こします。とはいえ、それが許可されると、列挙型のグループ間での共通の挙動を共有するという利点もあります。 (OrderedEnum の例を参照してください。)
8.13.10. Pickle 化¶
列挙型は pickle 化と unpickle 化が行えます:
>>> from test.test_enum import Fruit
>>> from pickle import dumps, loads
>>> Fruit.tomato is loads(dumps(Fruit.tomato))
True
通常の pickle 化の制限事項が適用されます: pickle 可能な列挙型はモジュールのトップレベルで定義されていなくてはならず、unpickle 化はモジュールからインポート可能でなければなりません。
注釈
pickle プロトコルバージョン 4 では他のクラスで入れ子になった列挙型の pickle 化も容易です。
Enum メンバーをどう pickle 化/unpickle 化するかは、列挙型クラス内の __reduce_ex__()
で定義することで変更できます。
8.13.11. 機能 API¶
Enum
クラスは呼び出し可能で、以下の機能 API を提供しています:
>>> Animal = Enum('Animal', 'ant bee cat dog')
>>> Animal
<enum 'Animal'>
>>> Animal.ant
<Animal.ant: 1>
>>> Animal.ant.value
1
>>> list(Animal)
[<Animal.ant: 1>, <Animal.bee: 2>, <Animal.cat: 3>, <Animal.dog: 4>]
この API の動作は namedtuple
と似ています。Enum
呼び出しの第 1 引数は列挙型の名前です。
第 2 引数は列挙型メンバー名の ソース です。空白で区切った名前の文字列、名前のシーケンス、キー/値のペアの 2 要素タプルのシーケンス、あるいは名前と値のマッピング (例: 辞書) を指定できます。最後の 2 個のオプションでは、列挙型へ任意の値を割り当てることができます。前の 2 つのオプションでは、1 から始まり増加していく整数を自動的に割り当てます (別の開始値を指定するには、start
引数を使用します)。Enum
から派生した新しいクラスが返されます。言い換えれば、上記の Animal
への割り当ては以下と等価です:
>>> class Animal(Enum):
... ant = 1
... bee = 2
... cat = 3
... dog = 4
...
デフォルトの開始番号が 0
ではなく 1
である理由は、0
がブール演算子では False
になりますが、すべての列挙型メンバーの評価は True
でなければならないためです。
機能 API による Enum の pickle 化は、その列挙型がどのモジュールで作成されたかを見つけ出すためにフレームスタックの実装の詳細が使われるので、トリッキーになることがあります (例えば別のモジュールのユーティリティ関数を使うと失敗しますし、IronPython や Jython ではうまくいきません)。解決策は、以下のようにモジュール名を明示的に指定することです:
>>> Animal = Enum('Animal', 'ant bee cat dog', module=__name__)
警告
module
が与えられない場合、Enum はそれがなにか決定できないため、新しい Enum メンバーは unpickle 化できなくなります; エラーをソースの近いところで発生させるため、pickle 化は無効になります。
新しい pickle プロトコルバージョン 4 では、一部の状況において、pickle がクラスを発見するための場所の設定に __qualname__
を参照します。例えば、そのクラスがグローバルスコープ内のクラス SomeData 内で利用可能とするには以下のように指定します:
>>> Animal = Enum('Animal', 'ant bee cat dog', qualname='SomeData.Animal')
完全な構文は以下のようになります:
Enum(value='NewEnumName', names=<...>, *, module='...', qualname='...', type=<mixed-in class>, start=1)
value: | 新しい Enum クラスに記録されるそれ自身の名前です。 |
---|---|
names: | Enum のメンバーです。 空白またはカンマで区切った文字列でも構いません (特記ない限り値は 1 から始まります): 'red green blue' | 'red,green,blue' | 'red, green, blue'
または名前のイテレータで: ['red', 'green', 'blue']
または (名前, 値) のペアのイテレータでも指定できます: [('cyan', 4), ('magenta', 5), ('yellow', 6)]
またはマッピングでも指定できます: {'chartreuse': 7, 'sea_green': 11, 'rosemary': 42}
|
module: | 新しい Enum クラスが見つかるモジュールの名前です。 |
qualname: | 新しい Enum クラスが見つかるモジュールの場所です。 |
type: | 新しい Enum クラスに混ぜるデータ型です。 |
start: | names のみが渡されたときにカウントを開始する数です。 |
バージョン 3.5 で変更: start 引数が追加されました。
8.13.12. 派生列挙型¶
8.13.12.1. IntEnum¶
Enum
の派生型で int
のサブクラスでもあります。IntEnum
のメンバーは整数と比較できます; 拡張により、異なる整数列挙型同士で比較できます:
>>> from enum import IntEnum
>>> class Shape(IntEnum):
... circle = 1
... square = 2
...
>>> class Request(IntEnum):
... post = 1
... get = 2
...
>>> Shape == 1
False
>>> Shape.circle == 1
True
>>> Shape.circle == Request.post
True
ただし、これらも標準の Enum
列挙型とは比較できません:
>>> class Shape(IntEnum):
... circle = 1
... square = 2
...
>>> class Color(Enum):
... red = 1
... green = 2
...
>>> Shape.circle == Color.red
False
IntEnum
の値は他の用途では整数のように振る舞います:
>>> int(Shape.circle)
1
>>> ['a', 'b', 'c'][Shape.circle]
'b'
>>> [i for i in range(Shape.square)]
[0, 1]
IntEnum
は (整数と比較できることと、他の無関係な列挙型への移行性により) 列挙型の一部の意味論的な約束に反しているため、列挙型は原則として Enum
を使うことを強く推奨します。IntEnum
はそれを使うしか方法がない特殊な場合のみの使用にとどめてください; 例えば、整数の定数を列挙型に置き換えたとき、それが整数であると想定しているコードに対する後方互換性のためにそう振る舞う必要がある場合などです。
8.13.12.2. その他¶
IntEnum
は enum
モジュールの一部ですが、単独での実装もとても簡単に行なえます:
class IntEnum(int, Enum):
pass
ここでは似たような列挙型の派生を定義する方法を紹介します; 例えば、StrEnum
は int
ではなく str
で複合させたものです。
いくつかのルール:
Enum
のサブクラスを作成するとき、複合させるデータ型は、基底クラスの並びでEnum
自身より先に記述しなければなりません (上記IntEnum
の例を参照)。Enum
はあらゆるデータ型のメンバーを持てますが、いったん追加のデータ型 (int
やstr
など) と複合させた後は、すべてのメンバーの値は、上記int
のようにそのデータ型でなければならなくなります。この制限はメソッドの追加に対してのみ適用されません。他のデータ型と複合させた場合、
value
属性は、それが等価であり、等価であると比較が行えても、列挙型メンバー自身とは 同じではありません。%-方式の書式: %s および %r はそれぞれ
Enum
クラスの__str__()
および__repr__()
を呼び出します。その他のコード (IntEnum の %i や %h など) は列挙型のメンバーを複合されたデータ型とみなします。str.format()
(またはformat()
) は mix-in データ型の__format__()
を使用します。Enum
クラスのstr()
やrepr()
を使用したい場合は、 !s や !r の書式コードを使用します。
8.13.13. 興味深い例¶
Enum
および IntEnum
は大多数の用途をカバーすることを期待していますが、そのすべてをカバーできているわけではありません。ここではいくつかの異なるデータ型を直接使用できる列挙型のレシピやそれを作成する例を紹介します。
8.13.13.1. AutoNumber¶
各列挙番号の値の指定を不要にします:
>>> class AutoNumber(Enum):
... def __new__(cls):
... value = len(cls.__members__) + 1
... obj = object.__new__(cls)
... obj._value_ = value
... return obj
...
>>> class Color(AutoNumber):
... red = ()
... green = ()
... blue = ()
...
>>> Color.green.value == 2
True
8.13.13.2. OrderedEnum¶
IntEnum
をベースとしないため、通常の Enum
の不変条件 (他の列挙型と比較できないなど) のままで、メンバーを順序付けできる列挙型です:
>>> class OrderedEnum(Enum):
... def __ge__(self, other):
... if self.__class__ is other.__class__:
... return self.value >= other.value
... return NotImplemented
... def __gt__(self, other):
... if self.__class__ is other.__class__:
... return self.value > other.value
... return NotImplemented
... def __le__(self, other):
... if self.__class__ is other.__class__:
... return self.value <= other.value
... return NotImplemented
... def __lt__(self, other):
... if self.__class__ is other.__class__:
... return self.value < other.value
... return NotImplemented
...
>>> class Grade(OrderedEnum):
... A = 5
... B = 4
... C = 3
... D = 2
... F = 1
...
>>> Grade.C < Grade.A
True
8.13.13.3. DuplicateFreeEnum¶
値が同じメンバーが見つかった場合、別名を作るのではなく、エラーを送出します:
>>> class DuplicateFreeEnum(Enum):
... def __init__(self, *args):
... cls = self.__class__
... if any(self.value == e.value for e in cls):
... a = self.name
... e = cls(self.value).name
... raise ValueError(
... "aliases not allowed in DuplicateFreeEnum: %r --> %r"
... % (a, e))
...
>>> class Color(DuplicateFreeEnum):
... red = 1
... green = 2
... blue = 3
... grene = 2
...
Traceback (most recent call last):
...
ValueError: aliases not allowed in DuplicateFreeEnum: 'grene' --> 'green'
注釈
これは Enum に別名を無効にするのと同様な振る舞いの追加や変更をおこなうためのサブクラス化に役立つ例です。単に別名を無効にしたいだけなら、 unique()
デコレーターを使用して行えます。
8.13.13.4. Planet¶
__new__()
や __init__()
が定義されている場合、列挙型メンバーの値はこれらのメソッドに渡されます:
>>> class Planet(Enum):
... MERCURY = (3.303e+23, 2.4397e6)
... VENUS = (4.869e+24, 6.0518e6)
... EARTH = (5.976e+24, 6.37814e6)
... MARS = (6.421e+23, 3.3972e6)
... JUPITER = (1.9e+27, 7.1492e7)
... SATURN = (5.688e+26, 6.0268e7)
... URANUS = (8.686e+25, 2.5559e7)
... NEPTUNE = (1.024e+26, 2.4746e7)
... def __init__(self, mass, radius):
... self.mass = mass # in kilograms
... self.radius = radius # in meters
... @property
... def surface_gravity(self):
... # universal gravitational constant (m3 kg-1 s-2)
... G = 6.67300E-11
... return G * self.mass / (self.radius * self.radius)
...
>>> Planet.EARTH.value
(5.976e+24, 6378140.0)
>>> Planet.EARTH.surface_gravity
9.802652743337129
8.13.14. Enum はどう違うのか?¶
Enum は Enum 派生クラスやそれらのインスタンス (メンバー) 双方の多くの側面に影響を及ぼすカスタムメタクラスを持っています。
8.13.14.1. Enum クラス¶
EnumMeta
メタクラスは、__contains__()
、__dir__()
、__iter__()
および標準的なクラスでは失敗するが Enum
クラスでは動作するその他のメソッド (list(Color) や some_var in Color など) を責任を持って提供します。EnumMeta
は最終的な Enum
クラスのさまざまなメソッド (__new__()
、__getnewargs__()
、__str__()
および __repr__()
) が正しいことを責任を持って保証します。
8.13.14.2. Enum メンバー (インスタンス)¶
Enum メンバーについて最も興味深いのは、それらがシングルトンであるということです。EnumMeta
は Enum
自身を作成し、メンバーを作成し、新しいインスタンスが作成されていないかどうかを確認するために既存のメンバーインスタンスだけを返すカスタム __new__()
を追加します。
8.13.14.3. 細かい点¶
Enum
メンバーは Enum
クラスのインスタンスであり、EnumClass.member としてアクセスできるものの、メンバーから直接アクセスすることはできません。直接アクセスをすると、その検索は失敗するか、さらに悪い場合には、探している Enum
メンバー以外のものを返す場合もあります:
>>> class FieldTypes(Enum):
... name = 0
... value = 1
... size = 2
...
>>> FieldTypes.value.size
<FieldTypes.size: 2>
>>> FieldTypes.size.value
2
バージョン 3.5 で変更.
__members__
はクラスでのみ利用可能です。
Enum
サブクラスに拡張メソッドを与えた場合、上述の Planet のように、それらメソッドはメンバーの dir()
に表示されますが、クラスのそれには表示されません:
>>> dir(Planet)
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH)
['__class__', '__doc__', '__module__', 'name', 'surface_gravity', 'value']
__new__()
メソッドは Enum
メンバーの作成にのみ使用され、その後置き換えられます。カスタムした __new__()
メソッドは必ずこのオブジェクトを作成し、 _value_
属性を適切に設定しなければなりません。
Enum
メンバーを調べる方法を変更したい場合は、 Enum
のサブクラスのためにヘルパー関数をまたは classmethod()
を書かなければなりません。