enum — Поддержка перечислений
Новое в версии 3.4.
Исходный код: Lib/enum.py
Перечисление — это набор символических имен (членов), связанных с уникальными константными значениями. В пределах перечисления члены могут сравниваться по идентичности, и само перечисление может быть итерировано.
1. Содержание модуля
Этот модуль определяет два класса перечислений, которые могут использоваться для определения уникальных наборов имен и значений: Enum и IntEnum. Он также определяет один декоратор, unique().
-
class enum.Enum -
Базовый класс для создания перечисленных констант. Обратитесь к разделу Функциональный API для альтернативной синтаксической конструкции.
-
class enum.IntEnum -
Базовый класс для создания перечисленных констант, которые также являются подклассами
int.
-
enum.unique() -
Декоратор класса Enum, гарантирующий, что только одно имя связано с любым одним значением.
2. Создание перечисления
Перечисления создаются с использованием синтаксиса 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, и т. д.)
Примечание
Несмотря на то, что мы используем синтаксис class для создания Enums, Enums не являются обычными классами Python. Подробнее см. Чем отличаются Enums?.
Члены перечисления имеют удобочитаемые строковые представления:
>>> print(Color.red) Color.red
…в то время как их repr содержит больше информации:
>>> print(repr(Color.red)) <Color.red: 1>
Тип члена перечисления — это перечисление, к которому он принадлежит:
>>> type(Color.red) <enum 'Color'> >>> isinstance(Color.green, Color) True >>>
У членов перечисления также есть свойство, которое содержит только имя элемента:
>>> 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
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
4. Дублирование членов и значений перечисления
Наличие двух членов перечисления с одинаковым именем недопустимо:
>>> class Shape(Enum): ... square = 2 ... square = 3 ... Traceback (most recent call last): ... TypeError: Attempted to reuse key: 'square'
Однако, два члена перечисления могут иметь одинаковое значение. Учитывая два члена 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>
Примечание
Попытка создать члена с тем же именем, что и уже определённый атрибут (другой член, метод и т. д.), или попытка создать атрибут с тем же именем, что и член, не допускается.
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
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']
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. Разрешенные члены и атрибуты перечислений
В приведенных выше примерах для значений перечисления используются целые числа. Использование целых чисел кратко и удобно (и по умолчанию предоставляется функциональным 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'
Правила того, что разрешено, таковы: имена, начинающиеся и заканчивающиеся одним подчёркиванием, зарезервированы перечислением и не могут использоваться; все остальные атрибуты, определённые в перечислении, станут членами этого перечисления, за исключением специальных методов (__str__(), __add__() и т. д.) и дескрипторов (методы также являются дескрипторами).
Примечание: если ваше перечисление определяет __new__() и/или __init__(), то любые значения, которые были переданы члену перечисления, будут переданы в эти методы. См. Planet для примера.
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 для примера.)
10. Сериализация
Перечисления можно сериализовать и десериализовать:
>>> from test.test_enum import Fruit >>> from pickle import dumps, loads >>> Fruit.tomato is loads(dumps(Fruit.tomato)) True
Применяются обычные ограничения для сериализации: сериализуемые перечисления должны быть определены на верхнем уровне модуля, так как десериализация требует их импортирования из этого модуля.
Примечание
С протоколом сериализации pickle версии 4 можно легко сериализовать перечисления, вложенные в другие классы.
Можно изменить способ сериализации/десериализации членов Enum, определив __reduce_ex__() в классе перечисления.
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 — имя перечисления.
Второй аргумент — источник имён членов перечисления. Он может быть строкой имён, разделённой пробелами, последовательностью имён, последовательностью пар из 2-х кортежей с ключами/значениями или отображением (например, словарем) имён на значения. Последние два варианта позволяют назначать произвольные значения перечислениям; другие автоматически назначают увеличивающиеся целые числа, начиная с 1 (используйте параметр start для указания другого начального значения). Возвращается новый класс, полученный от Enum. Другими словами, приведённое выше присваивание Animal эквивалентно:
>>> class Animal(Enum): ... ant = 1 ... bee = 2 ... cat = 3 ... dog = 4 ...
Причина выбора значения 1 в качестве начального числа, а не 0, заключается в том, что 0 в булевом смысле является False, но члены перечисления всегда оцениваются как True.
Десериализация перечислений, созданных с помощью функционального API, может быть сложной задачей, так как при реализации стека фреймов используется информация о том, в каком модуле создаётся перечисление (например, произойдёт ошибка, если вы используете служебную функцию в отдельном модуле, а также она может не работать в IronPython или Jython). Решение состоит в явном указании имени модуля следующим образом:
>>> Animal = Enum('Animal', 'ant bee cat dog', module=__name__)
Предупреждение
Если module не указано, и Enum не может определить, что это такое, новые члены перечисления не будут поддерживать десериализацию; для локализации ошибок, сериализация будет отключена.
Новый протокол сериализации 4 также в некоторых случаях полагается на __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: |
Имя, которое новый класс перечисления будет записывать как своё имя. |
|---|---|
| names: |
Члены перечисления. Это может быть строка, разделённая пробелами или запятыми (значения будут начинаться с 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: |
Имя модуля, в котором можно найти новый класс перечисления. |
| qualname: |
Местоположение нового класса перечисления в модуле. |
| type: |
Тип, который нужно добавить в новый класс перечисления. |
| start: |
Число, с которого начинается счёт, если передаются только имена. |
Изменено в версии 3.5: Добавлен параметр start.
12. Производные перечисления
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]
Для подавляющего большинства случаев рекомендуется использовать Enum, так как IntEnum нарушает некоторые семантические обещания перечисления (путём сравнимости с целыми числами, и, следовательно, по транзитивности с другими несвязанными перечислениями). Его следует использовать только в особых случаях, когда нет другого выбора; например, когда целочисленные константы заменяются перечислениями и требуется обратная совместимость с кодом, который всё ещё ожидает целые числа.
12.2. Другие
Хотя IntEnum является частью модуля enum, было бы очень просто реализовать его независимо:
class IntEnum(int, Enum):
pass
Это демонстрирует, как можно определить аналогичные производные перечисления; например, StrEnum, который включает str вместо int.
Некоторые правила:
- При наследовании от
Enum, типы смешения должны появляться доEnumв списке базовых классов, как в примере сIntEnum. - Хотя члены
Enumмогут быть любого типа, после включения дополнительного типа все члены должны иметь значения этого типа, например,intвыше. Это ограничение не относится к смешениям, которые только добавляют методы и не определяют другой тип данных, такие какintилиstr. - Когда добавляется другой тип данных, атрибут
valueне такой же, как сам член перечисления, хотя он эквивалентен и будет сравниваться как равный. - Форматирование по образцу «%-стиль»:
%sи%rвызывают методыEnumкласса__str__()и__repr__()соответственно; другие коды (например,%iили%hдля IntEnum) обрабатывают член перечисления как значение его смешанного типа. -
str.format()(илиformat()) будет использовать__format__()смешанного типа. Если нужен атрибутEnumклассаstr()илиrepr(), используйте коды форматирования!sили!r.
13. Интересные примеры
Хотя Enum и IntEnum ожидаются для покрытия большинства случаев использования, они не могут охватить все. Ниже приведены рецепты для различных типов перечислений, которые можно использовать непосредственно или в качестве примеров для создания собственных.
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
Примечание
Метод __new__(), если он определён, используется во время создания членов перечисления; затем он заменяется методом __new__() перечисления, который используется после создания класса для поиска существующих членов.
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
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() вместо этого.
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
14. Отличия перечислений?
Перечисления имеют пользовательский метакласс, который влияет на многие аспекты как производных классов перечислений, так и их экземпляров (членов).
14.1. Классы перечислений
Метакласс EnumMeta отвечает за предоставление методов __contains__(), __dir__(), __iter__() и других, которые позволяют выполнять действия с классом Enum, которые не работают с типичным классом, таким как list(Color) или some_var in Color. EnumMeta отвечает за обеспечение корректности различных других методов конечного класса Enum (таких как __new__(), __getnewargs__(), __str__() и __repr__()).
14.2. Члены перечисления (или экземпляры)
Самое интересное в членах перечисления заключается в том, что они являются синглтонами. EnumMeta создаёт их все во время создания класса Enum и затем устанавливает пользовательский метод __new__(), гарантируя, что новые экземпляры не будут создаваться, возвращая только существующие члены.
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, то вам следует либо написать вспомогательную функцию, либо метод classmethod() для подкласса Enum.
© 2001–2020 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.5/library/enum.html