Spec-Zone.ru › Python 3.5

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.

Некоторые правила:

  1. При наследовании от Enum, типы смешения должны появляться до Enum в списке базовых классов, как в примере с IntEnum.
  2. Хотя члены Enum могут быть любого типа, после включения дополнительного типа все члены должны иметь значения этого типа, например, int выше. Это ограничение не относится к смешениям, которые только добавляют методы и не определяют другой тип данных, такие как int или str.
  3. Когда добавляется другой тип данных, атрибут value не такой же, как сам член перечисления, хотя он эквивалентен и будет сравниваться как равный.
  4. Форматирование по образцу «%-стиль»: %s и %r вызывают методы Enum класса __str__() и __repr__() соответственно; другие коды (например, %i или %h для IntEnum) обрабатывают член перечисления как значение его смешанного типа.
  5. 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API