enum — Поддержка перечислений

Добавлено в версии 3.4.

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


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

Примечание

Регистр полей Enum

Поскольку перечисления используются для представления констант, мы рекомендуем использовать имена полей В_ВЕРХНЕМ_РЕГИСТРЕ для перечисления и будем использовать этот стиль в наших примерах.

Содержание модуля

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

class enum.Enum

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

class enum.IntEnum

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

class enum.IntFlag

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

class enum.Flag

Базовый класс для создания нумерованных констант, которые можно комбинировать с помощью побитовых операций без потери принадлежности к Flag.

enum.unique()

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

class enum.auto

Экземпляры заменяются соответствующим значением для полей Enum. Начальное значение начинается с 1.

Добавлено в версии 3.6: Flag, IntFlag, auto

Создание Enum

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

>>> from enum import Enum
>>> class Color(Enum):
...     RED = 1
...     GREEN = 2
...     BLUE = 3
...

Примечание

Перечисление значений поля

Значением поля может быть что угодно: int, str и т. д.. Если точное значение неважно, вы можете использовать auto сущности и для вас будет выбрано подходящее значение. Если вы смешиваете auto с другими значениями, необходимо соблюдать осторожность.

Примечание

Формальности

  • Класс Color является перечислением (или enum)
  • Атрибуты Color.RED, Color.GREEN и т. д. являются полями перечисления (или enum поля) и функционально постоянными.
  • У полей перечислений есть имена и значения (имя Color.REDRED, значение Color.BLUE - 3 и т. д.)

Примечание

Даже при том, что мы используем синтаксис class, чтобы создать Enum’ы, Enum’ы — не обычные классы Python. Дополнительные сведения см. в разделе Чем Enum’ы отличаются?.

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

>>> print(Color.RED)
Color.RED

… в то время как их repr возвращает больше информации:

>>> print(repr(Color.RED))
<Color.RED: 1>

type поле перечисления является перечислением, которому оно принадлежит:

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

Программный доступ к полям перечисления и их атрибутам

Иногда бывает полезно получить доступ к полям в перечислениях программно (например, в ситуациях, когда 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

Дублирование enum полей и значений

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

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

Примечание

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

Обеспечение уникальных значений перечисления

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

@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

Использование автоматических значений

Если точное значение неважно, можно использовать auto:

>>> from enum import Enum, auto
>>> class Color(Enum):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Значения выбираются _generate_next_value_(), которые могут быть переопределены:

>>> class AutoName(Enum):
...     def _generate_next_value_(name, start, count, last_values):
...         return name
...
>>> class Ordinal(AutoName):
...     NORTH = auto()
...     SOUTH = auto()
...     EAST = auto()
...     WEST = auto()
...
>>> list(Ordinal)
[<Ordinal.NORTH: 'NORTH'>, <Ordinal.SOUTH: 'SOUTH'>, <Ordinal.EAST: 'EAST'>, <Ordinal.WEST: 'WEST'>]

Примечание

Цель методов _generate_next_value_() по умолчанию — предоставить следующий int в последовательности с последним предоставленным int, но способ его выполнения является деталью реализации и может измениться.

Примечание

Метод _generate_next_value_() должен быть определен перед любыми полями.

Итерация

Итерация по элементам перечисления не предоставляет псевдонимов:

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

Сравнения

Поля перечисления сравниваются по идентификатору:

>>> 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: '<' not supported between instances of 'Color' and 'Color'

Несмотря на то, что сравнения на равенство:

>>> Color.BLUE == Color.RED
False
>>> Color.BLUE != Color.RED
True
>>> Color.BLUE == Color.BLUE
True

Сравнения с неперечисляемыми значениями всегда будут сравниваться не равными (опять же, IntEnum был явно разработан, чтобы вести себя по-другому, см. ниже):

>>> Color.BLUE == 2
False

Допустимые поля и атрибуты перечислений

В приведенных выше примерах для значений перечисления использовались целые числа. Использование целых чисел является коротким и удобным (и предоставляется по умолчанию Функциональный 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'

Правила для того, что разрешено, следующие: имена, которые начинаются и заканчиваются одним подчеркиванием, зарезервированы перечислением и не могут использоваться; все другие атрибуты, определенные в рамках перечисления, становятся полями этого перечисления, за исключением специальных методов (__str__(), __add__() и т.д.), дескрипторы (методы дескрипторов) и имена переменных, перечисленные в _ignore_.

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

Ограничение подклассов Enum

Новый класс Enum должен содержать один базовый класс Enum, до одного конкретного типа данных и столько классов миксинов на основе object, сколько необходимо. Порядок этих базовых классов:

class EnumName([mix-in, ...,] [data-type,] base-enum):
    pass

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

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

Пиклинг

Перечисления могут быть пиклены (pickled) и анпиклены (unpickled):

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

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

Примечание

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

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

Функциональный 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 не может определить, что это такое, новые поля Enum не будут анпиклены; чтобы ошибки были ближе к источнику, пиклинг будет отключено.

Новый протокол 4 pickle также, при некоторых обстоятельствах, полагается на __qualname__, устанавливаемый в местоположение, где pickle будет в состоянии найти класс. Например, если класс был доступен в классе 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:

число для начала подсчета, если будут переданы только имена.

Изменено в версии 3.5: добавлен Был добавлен параметр start.

Производные перечисления

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]

IntFlag

Следующий предоставленный вариант EnumIntFlag, также основан на int. Разница в том, что поля IntFlag могут быть объединены с помощью побитовых операторов (&, |, ^, ~), и результатом по-прежнему будет поле IntFlag. Однако, как следует из названия, поля IntFlag также являются подклассом int и могут использоваться везде, где используется int. Любая операция с полем IntFlag, кроме побитовых, теряет членство IntFlag.

Добавлено в версии 3.6.

Типовой класс IntFlag:

>>> from enum import IntFlag
>>> class Perm(IntFlag):
...     R = 4
...     W = 2
...     X = 1
...
>>> Perm.R | Perm.W
<Perm.R|W: 6>
>>> Perm.R + Perm.W
6
>>> RW = Perm.R | Perm.W
>>> Perm.R in RW
True

Также можно назвать комбинации:

>>> class Perm(IntFlag):
...     R = 4
...     W = 2
...     X = 1
...     RWX = 7
>>> Perm.RWX
<Perm.RWX: 7>
>>> ~Perm.RWX
<Perm.-8: -8>

Другое важное отличие между IntFlag и Enum состоит в том, что если флаги не установлены (значение равно 0), его логическая оценка является False:

>>> Perm.R & Perm.X
<Perm.0: 0>
>>> bool(Perm.R & Perm.X)
False

Поскольку поля IntFlag также подклассы int, они могут быть объединены с ними:

>>> Perm.X | 8
<Perm.8|X: 9>

Flag

Последний вариант — Flag. Как и IntFlag, поля Flag можно комбинировать с помощью побитовых операторов (&, |, ^, ~). В отличие от IntFlag, они не могут быть объединены или сравнены ни с каким другим перечислением Flag или int. Хотя можно указать значения напрямую, рекомендуется использовать auto в качестве значения и позволить Flag выбрать подходящее значение.

Добавлено в версии 3.6.

Как и IntFlag, если комбинация полей Flag не приводит к установке флагов, логическая оценка будет False:

>>> from enum import Flag, auto
>>> class Color(Flag):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.RED & Color.GREEN
<Color.0: 0>
>>> bool(Color.RED & Color.GREEN)
False

Отдельные флаги должны содержать значения, являющиеся степенями двойки (1, 2, 4, 8, …), а комбинации флагов — нет:

>>> class Color(Flag):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...     WHITE = RED | BLUE | GREEN
...
>>> Color.WHITE
<Color.WHITE: 7>

Присвоение имени условию «флаги не установлены» не изменяет его логическое значение:

>>> class Color(Flag):
...     BLACK = 0
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.BLACK
<Color.BLACK: 0>
>>> bool(Color.BLACK)
False

Примечание

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

Другие

Хотя IntEnum является частью модуля enum, его было бы очень просто реализовать независимо:

class IntEnum(int, Enum):
    pass

Это демонстрирует, как можно определить аналогичные производные перечисления; например, StrEnum, который смешивается в str вместо int.

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

  1. При создании подкласса Enum типы миксины должны появляться перед самим Enum в последовательности баз, как в примере IntEnum выше.
  2. Хотя у Enum могут быть поля любого типа, после добавления дополнительного типа у всех полей должны быть значения этого типа, например int выше. Это ограничение не распространяется на миксины, которые только добавляют методы и не указывают другой тип данных, например int или str.
  3. При смешивании другого типа данных атрибут value не совпадает с самим полем перечисления, хотя он эквивалентен и будет сравниваться равным.
  4. %-стиль форматирования: % s и`% r` вызывают __str__() и __repr__() класса Enum соответственно; другие коды (такие как %i или %h для IntEnum) рассматривают поле перечисления как его тип миксин.
  5. Отформатированные строковые литералы, str.format() и format() будут использовать __format__() типа миксина, если __str__() или __format__() не переопределены в подклассе, и в этом случае будут использоваться переопределенные методы или методы Enum. Используйте коды формата !s и !r, чтобы принудительно использовать методы __str__() и __repr__() класса Enum.

Когда использовать __new__() против __init__()

__new__() необходимо использовать всякий раз, когда вы хотите настроить фактическое значение поля Enum. Любые другие модификации могут входить в __new__() или __init__(), причем __init__() предпочтительнее.

Например, если вы хотите передать конструктору несколько элементов, но только один из них должен быть значением:

>>> class Coordinate(bytes, Enum):
...     """
...     Координация с двоичными кодами, которые могут быть проиндексированы кодом int.
...     """
...     def __new__(cls, value, label, unit):
...         obj = bytes.__new__(cls, [value])
...         obj._value_ = value
...         obj.label = label
...         obj.unit = unit
...         return obj
...     PX = (0, 'P.X', 'km')
...     PY = (1, 'P.Y', 'km')
...     VX = (2, 'V.X', 'km/s')
...     VY = (3, 'V.Y', 'km/s')
...

>>> print(Coordinate['PY'])
Coordinate.PY

>>> print(Coordinate(3))
Coordinate.VY

Интересные примеры

Хотя ожидается, что Enum, IntEnum, IntFlag и Flag охватят большинство случаев использования, они не могут охватить их все. Далее приведены рецепты для некоторых различных типов перечислений, которые могут быть использованы непосредственно, или в качестве примеров для создания своего кода.

Пропуск значений

Во многих случаях использования не важно, каково фактическое значение перечисления. Существует несколько способов определения простого типа перечисления:

  • использование сущности auto для значения
  • использования сущности object в качестве значения
  • использовать описательный строки в качестве значения
  • использовать кортеж в качестве значение и пользовательский __new__() для замены кортежа на int значение

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

Какой бы метод вы ни выбрали, вы должны предоставить repr(), который также скрывает (неважное) значение:

>>> class NoValue(Enum):
...     def __repr__(self):
...         return '<%s.%s>' % (self.__class__.__name__, self.name)
...

Использование auto

Использование auto будет выглядеть как:

>>> class Color(NoValue):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.GREEN
<Color.GREEN>

Использование object

Использование object будет выглядеть как:

>>> class Color(NoValue):
...     RED = object()
...     GREEN = object()
...     BLUE = object()
...
>>> Color.GREEN
<Color.GREEN>

Использование описательной строки

Использование строки в качестве значения будет выглядеть как:

>>> class Color(NoValue):
...     RED = 'stop'
...     GREEN = 'go'
...     BLUE = 'too fast!'
...
>>> Color.GREEN
<Color.GREEN>
>>> Color.GREEN.value
'go'

Использование пользовательских __new__()

Используя автоматическую нумерацию __new__() был бы похож:

>>> class AutoNumber(NoValue):
...     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
<Color.GREEN>
>>> Color.GREEN.value
2

Чтобы сделать AutoNumber более универсальным, добавьте в сигнатуру *args:

>>> class AutoNumber(NoValue):
...     def __new__(cls, *args):      # это единственное изменение сверху
...         value = len(cls.__members__) + 1
...         obj = object.__new__(cls)
...         obj._value_ = value
...         return obj
...

Затем, унаследовавшись от AutoNumber, вы можете написать свой собственный __init__ для обработки любых дополнительных аргументов:

>>> class Swatch(AutoNumber):
...     def __init__(self, pantone='unknown'):
...         self.pantone = pantone
...     AUBURN = '3497'
...     SEA_GREEN = '1246'
...     BLEACHED_CORAL = () # Новый цвет, кода Pantone пока нет!
...
>>> Swatch.SEA_GREEN
<Swatch.SEA_GREEN: 2>
>>> Swatch.SEA_GREEN.pantone
'1246'
>>> Swatch.BLEACHED_CORAL.pantone
'unknown'

Примечание

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

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

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().

Планета

Если определён __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

TimePeriod

Пример использования _ignore_ атрибута:

>>> from datetime import timedelta
>>> class Period(timedelta, Enum):
...     "различные отрезки времени"
...     _ignore_ = 'Period i'
...     Period = vars()
...     for i in range(367):
...         Period['day_%d' % i] = i
...
>>> list(Period)[:2]
[<Period.day_0: datetime.timedelta(0)>, <Period.day_1: datetime.timedelta(days=1)>]
>>> list(Period)[-2:]
[<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>]

Чем Enum’ы отличаются?

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

Классы Enum

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

Поля Enum (иначе сущности)

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

Нюансы

Поддержка __dunder__ имён

__members__ является упорядоченным отображением элементов member_name:member только для чтения. Он доступен только в классе.

__new__(), если он указан, должен создавать и возвращать поля перечисления; Также очень хорошей идеей будет установить соответствующее поле _value_. После создания всех полей он больше не используется.

Поддержка _sunder_ имён

  • _name_ – название поля
  • _value_ – значение поля; может быть установлен/изменен в __new__
  • _missing_ - функция поиска, используемая, когда значение не найдено; может быть переопределено
  • _ignore_ — список имён в виде list() или str(), которые не будут преобразованы в поля и будут удалены из окончательного класса
  • _order_ — используется в коде Python 2/3 для обеспечения согласованности порядка полей (атрибут класса, удаляется во время создания класса)
  • _generate_next_value_ — используется Функциональный API и auto для получения подходящего значения для поля перечисления; может быть переопределено

Добавлено в версии 3.6: _missing_, _order_, _generate_next_value_

Добавлено в версии 3.7: _ignore_

Для обеспечения синхронизации кода Python 2/Python 3 может быть предоставлен атрибут _order_. Он будет проверяться на соответствие фактическому порядку перечисления и вызывать ошибку, если они не совпадают:

>>> class Color(Enum):
...     _order_ = 'RED GREEN BLUE'
...     RED = 1
...     BLUE = 3
...     GREEN = 2
...
Traceback (most recent call last):
...
TypeError: member order does not match _order_

Примечание

В коде Python 2 атрибут _order_ необходим, поскольку порядок определения теряется до того, как он может быть записан.

Enum тип поля

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

>>> class FieldTypes(Enum):
...     name = 0
...     value = 1
...     size = 2
...
>>> FieldTypes.value.size
<FieldTypes.size: 2>
>>> FieldTypes.size.value
2

Изменено в версии 3.5.

Булево значение классов Enum и полей

Поля Enum, смешанные с типами, отличными от Enum (такими как int, str и т. д.), вычисляются в соответствии с правилами смешанного типа; в противном случае все поля вычиляются как True. Чтобы сделать вашу собственную логическую оценку Enum зависимой от значения поля, добавьте в свой класс следующее:

def __bool__(self):
    return bool(self.value)

Enum классы всегда вычисляются как True.

Enum классы с методами

Если вы предоставите своему подклассу Enum дополнительные методы, такие как класс Планета выше, эти методы будут отображаться в 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']

Объединение полей Flag

Если комбинация полей Flag не названа, repr() будет включать все именованные флаги и все именованные комбинации флагов, которые находятся в значении:

>>> class Color(Flag):
...     RED = auto()
...     GREEN = auto()
...     BLUE = auto()
...     MAGENTA = RED | BLUE
...     YELLOW = RED | GREEN
...     CYAN = GREEN | BLUE
...
>>> Color(3)  # именованная комбинация
<Color.YELLOW: 3>
>>> Color(7)      # неназванная комбинация
<Color.CYAN|MAGENTA|BLUE|YELLOW|GREEN|RED: 7>