Документация Python неофициальный перевод
Содержание страницы

8.13. enum – Поддержка перечисленийenum – Support for enumerations

Новое в версии 3.4.

Исходный код: Lib/enum.py


Перечисление – это набор символических имён (членов), привязанных к уникальным постоянным значениям. Внутри перечисления члены можно сравнивать по идентичности, а само перечисление можно перебирать.

8.13.1. Содержание модуляModule Contents

Этот модуль определяет два класса перечислений, которые можно использовать для определения уникальных наборов имён и значений: Enum и IntEnum. Он также определяет один декоратор, unique().

class enum.Enum

Базовый класс для создания перечислимых констант. См. раздел Функциональный API для альтернативного синтаксиса создания.

class enum.IntEnum

Базовый класс для создания перечисляемых констант, которые также являются подклассами int.

enum.unique()

Декоратор класса перечисления, который гарантирует, что каждому значению соответствует только одно имя.

8.13.2. Создание EnumCreating an Enum

Перечисления создаются с помощью синтаксиса class, что делает их лёгкими для чтения и написания. Альтернативный метод создания описан в Функциональном API. Чтобы определить перечисление, создайте подкласс Enum следующим образом:

>>> from enum import Enum
>>> class Color(Enum):
...     red = 1
...     green = 2
...     blue = 3
...

Примечание

Терминология

  • Класс Color является перечислением (или enum)
  • Атрибуты Color.red, Color.green и т.д. являются членами перечисления (или членами enum).
  • Члены enum имеют имена и значения (имя Color.redred, значение Color.blue3 и т.д.)

Примечание

Хотя мы используем синтаксис class для создания перечислений, перечисления не являются обычными классами Python. Смотрите Чем перечисления отличаются? для получения дополнительных сведений.

Члены перечисления имеют удобочитаемые строковые представления:

>>> 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

8.13.3. Программный доступ к членам перечисления и их атрибутамProgrammatic access to enumeration members and their attributes

Иногда полезно получать доступ к членам перечислений программно (т.е. в ситуациях, когда Color.red не подходит, потому что точный цвет неизвестен во время написания программы). Enum предоставляет такой доступ:

>>> Color(1)
<Color.red: 1>
>>> Color(3)
<Color.blue: 3>

Если требуется обратиться к членам перечисления по имени, используйте доступ по ключу:

>>> Color['red']
<Color.red: 1>
>>> Color['green']
<Color.green: 2>

Если у вас есть член перечисления и вам нужно его имя или значение:

>>> member = Color.red
>>> member.name
'red'
>>> member.value
1

8.13.4. Дублирование членов enum и значенийDuplicating enum members and values

Наличие двух членов перечисления с одинаковым именем недопустимо:

>>> 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>

Примечание

Попытка создать член с тем же именем, что и уже определённый атрибут (другой член, метод и т.д.), или попытка создать атрибут с тем же именем, что и член, не допускается.

8.13.5. Обеспечение уникальности значений перечисленияEnsuring unique enumeration values

По умолчанию перечисления допускают несколько имён в качестве псевдонимов для одного и того же значения. Если такое поведение нежелательно, можно использовать следующий декоратор, чтобы гарантировать, что каждое значение используется в перечислении только один раз:

@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. ИтерацияIteration

Перебор членов перечисления не включает псевдонимы:

>>> 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. СравненияComparisons

Члены перечисления сравниваются по идентичности:

>>> Color.red is Color.red
True
>>> Color.red is Color.blue
False
>>> Color.red is not Color.blue
True

Упорядоченные сравнения между значениями перечисления не поддерживаются. Члены перечисления – не целые числа (но см. 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. Допустимые члены и атрибуты перечисленийAllowed members and attributes of enumerations

В примерах выше для значений перечислений используются целые числа. Использование целых чисел – это кратко и удобно (и предусмотрено по умолчанию функциональным API), но не является строгим требованием. В подавляющем большинстве случаев не важно, каково фактическое значение элемента перечисления. Но если значение является важным, перечисления могут иметь произвольные значения.

Перечисления – это классы Python и могут иметь обычные методы и специальные методы. Если есть такое перечисление:

>>> class Mood(Enum):
...     funky = 1
...     happy = 3
...
...     def describe(self):
...         # self – это член перечисления
...         return self.name, self.value
...
...     def __str__(self):
...         return 'my custom str! {0}'.format(self.value)
...
...     @classmethod
...     def favorite_mood(cls):
...         # cls здесь – это перечисление
...         return cls.happy
...

Тогда:

>>> Mood.favorite_mood()
<Mood.happy: 3>
>>> Mood.happy.describe()
('happy', 3)
>>> str(Mood.funky)
'my custom str! 1'

Правила для того, что разрешено, таковы: имена _sunder_ (начинающиеся и заканчивающиеся одним подчёркиванием) зарезервированы перечислением и не могут использоваться; все остальные атрибуты, определённые в перечислении, станут членами этого перечисления, за исключением имён __dunder__ и дескрипторов (методы также являются дескрипторами).

Примечание: если ваше перечисление определяет __new__() и/или __init__(), то любые значения, переданные члену перечисления, будут переданы этим методам. Смотрите Planet для примера.

8.13.9. Ограниченное наследование перечисленийRestricted subclassing of enumerations

Создание подкласса перечисления разрешено только в том случае, если перечисление не определяет никаких членов. Так делать запрещено:

>>> 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. СериализацияPickling

Перечисления могут быть упакованы и распакованы с помощью модуля pickle.

>>> from test.test_enum import Fruit
>>> from pickle import dumps, loads
>>> Fruit.tomato is loads(dumps(Fruit.tomato))
True

Обычные ограничения для упаковки с помощью модуля pickle применяются: перечисления, которые можно упаковать, должны быть определены на верхнем уровне модуля, так как для распаковки их нужно импортировать из этого модуля.

Примечание

С протоколом pickle версии 4 можно легко упаковывать перечисления, вложенные в другие классы.

Можно изменить способ сериализации/десериализации членов Enum, определив __reduce_ex__() в классе перечисления.

8.13.11. Функциональный APIFunctional 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. Возвращается новый класс, производный от Enum. Другими словами, приведённое выше присваивание Animal эквивалентно:

>>> class Animal(Enum):
...     ant = 1
...     bee = 2
...     cat = 3
...     dog = 4
...

Причина выбора 1 в качестве начального числа, а не 0, заключается в том, что 0 является False в логическом смысле, но все члены enum вычисляются в True.

Сериализация перечислений, созданных с помощью функционального API, может быть сложной, поскольку для определения модуля, в котором создаётся перечисление, используются детали реализации стека вызовов (например, это не сработает, если использовать вспомогательную функцию в отдельном модуле, а также может не работать в IronPython или Jython). Решение – явно указать имя модуля следующим образом:

>>> Animal = Enum('Animal', 'ant bee cat dog', module=__name__)

Предупреждение

Если module не указан и Enum не может определить его, новые члены Enum не смогут быть десериализованы; чтобы ошибки оставались ближе к источнику, сериализация будет отключена.

Новый протокол pickle 4 также в некоторых обстоятельствах полагается на установку __qualname__ в место, где pickle сможет найти класс. Например, если класс был сделан доступным в классе SomeData в глобальной области видимости:

>>> Animal = Enum('Animal', 'ant bee cat dog', qualname='SomeData.Animal')

Полная сигнатура:

Enum(value='NewEnumName', names=<...>, *, module='...', qualname='...', type=<mixed-in class>)
значение:

Что новый класс Enum запишет в качестве своего имени.

имена:

Члены 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}
модуль:

имя модуля, в котором находится новый класс Enum.

qualname:

где в модуле находится новый класс Enum.

тип:

тип, который следует примешать к новому классу Enum.

8.13.12. Производные перечисленияDerived Enumerations

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]

Для подавляющего большинства кода настоятельно рекомендуется Enum, поскольку IntEnum нарушает некоторые семантические обещания перечисления (из-за возможности сравнения с целыми числами, а следовательно, по транзитивности и с другими не связанными перечислениями). Его следует использовать только в особых случаях, когда нет другого выбора; например, когда целочисленные константы заменяются перечислениями и требуется обратная совместимость с кодом, который всё ещё ожидает целые числа.

8.13.12.2. ДругиеOthers

Хотя 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's __str__() и __repr__() соответственно; другие коды (например, %i или %h для IntEnum) обрабатывают член перечисления как его примешанный тип.
  5. str.__format__() (или format()) будет использовать метод __format__() примешанного типа. Если требуется получить str() или repr() класса Enum, используйте коды форматирования !s или !r str.

8.13.13. Интересные примерыInteresting examples

Хотя ожидается, что 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

Примечание

Метод __new__(), если он определён, используется при создании членов Enum; затем он заменяется методом __new__() класса Enum, который используется после создания класса для поиска существующих членов.

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       # в килограммах
...         self.radius = radius   # в метрах
...     @property
...     def surface_gravity(self):
...         # гравитационная постоянная (м3 кг-1 с-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. Чем отличаются перечисления?How are Enums different?

Перечисления имеют собственный метакласс, который влияет на многие аспекты как производных классов Enum, так и их экземпляров (членов).

8.13.14.1. Классы перечисленийEnum Classes

Метакласс EnumMeta отвечает за предоставление методов __contains__(), __dir__(), __iter__() и других, которые позволяют выполнять с классом Enum действия, невозможные для обычного класса, например list(Color) или some_var in Color. EnumMeta отвечает за то, чтобы различные другие методы в итоговом классе Enum были корректны (например, __new__(), __getnewargs__(), __str__() и __repr__()).

8.13.14.2. Члены перечисления (экземпляры)Enum Members (aka instances)

Самое интересное в членах Enum – то, что они являются синглтонами. EnumMeta создаёт их все во время создания самого класса Enum, а затем устанавливает собственную реализацию __new__(), чтобы гарантировать, что новые экземпляры никогда не будут созданы – возвращаются только существующие экземпляры членов.

8.13.14.3. ТонкостиFiner Points

Члены Enum являются экземплярами класса Enum, и хотя к ним можно обратиться как EnumClass.member, они не доступны напрямую из самого члена:

>>> Color.red
<Color.red: 1>
>>> Color.red.blue
Traceback (most recent call last):
...
AttributeError: 'Color' object has no attribute 'blue'

Аналогично, __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.