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

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

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


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

Примечание

Случай с членами Enum

Поскольку Enums используются для представления констант, мы рекомендуем использовать UPPER_CASE имена для членов enum, и мы будем использовать этот стиль в наших примерах.

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

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

class enum.Enum

Базовый класс для создания перечислимых констант. Альтернативный синтаксис построения см. в разделе Functional 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

Создание переменной

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

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

Примечание

Значения членов Enum

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

Примечание

Номенклатура

  • Класс Color представляет собой перечисление (или enum)

  • Атрибуты Color.RED, Color.GREEN и т.д. являются членами перечисления (или членами перечисления) и функционально являются константами.

  • Члены перечисления имеют имена и значения (имя Color.RED - RED, значение Color.BLUE - 3 и т.д.).

Примечание

Несмотря на то, что для создания Enums мы используем синтаксис class, Enums не являются обычными классами Python. Более подробную информацию смотрите в How are Enums different?.

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

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

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

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

Тип* члена перечисления - это перечисление, к которому он принадлежит:

>>> type(Color.RED)
<enum 'Color'>
>>> isinstance(Color.GREEN, Color)
True
>>>

Члены Enum также имеют свойство, которое содержит только имя элемента:

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

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

>>> class Shake(Enum):
...     VANILLA = 7
...     CHOCOLATE = 4
...     COOKIES = 9
...     MINT = 3
...
>>> for shake in Shake:
...     print(shake)
...
Shake.VANILLA
Shake.CHOCOLATE
Shake.COOKIES
Shake.MINT

Члены перечисления хешируются, поэтому их можно использовать в словарях и наборах:

>>> apples = {}
>>> apples[Color.RED] = 'red delicious'
>>> apples[Color.GREEN] = 'granny smith'
>>> apples == {Color.RED: 'red delicious', Color.GREEN: 'granny smith'}
True

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

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

>>> Color(1)
<Color.RED: 1>
>>> Color(3)
<Color.BLUE: 3>

Если вы хотите получить доступ к членам перечисления по имени, используйте элемент access:

>>> Color['RED']
<Color.RED: 1>
>>> Color['GREEN']
<Color.GREEN: 2>

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

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

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

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

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

Упорядоченные сравнения между значениями перечислений не поддерживаются. Члены перечисления не являются целыми числами (но см. 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

Разрешенные члены и атрибуты перечислений

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

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

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

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

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

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

>>> class MoreColor(Color):
...     PINK = 17
...
Traceback (most recent call last):
...
TypeError: MoreColor: cannot extend enumeration 'Color'

Но это разрешено:

>>> class Foo(Enum):
...     def some_behavior(self):
...         pass
...
>>> class Bar(Foo):
...     HAPPY = 1
...     SAD = 2
...

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

Маринование

Перечисления могут быть маринованными и не маринованными:

>>> 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 является имя перечисления.

Второй аргумент - это источник имен членов перечисления. Это может быть строка имен, разделенных пробелами, последовательность имен, последовательность кортежей с парами ключ/значение или отображение (например, словарь) имен на значения. Последние два варианта позволяют присваивать перечислениям произвольные значения; остальные автоматически присваивают возрастающие целые числа, начиная с 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 не будут распикированы; чтобы сохранить ошибки ближе к источнику, распикировка будет отключена.

Новый протокол 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>, start=1)
значение

То, что новый класс 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.

запустить

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

Изменено в версии 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

Следующий вариант Enum, IntFlag, также основан на 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. Как и 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 выше. Это ограничение не относится к смешениям, которые добавляют только методы и не указывают другой тип.

  3. Когда примешивается другой тип данных, атрибут value является не тем же самым, что и сам член перечисления, хотя он эквивалентен и будет сравниваться одинаково.

  4. Форматирование в стиле %: %s and %r call the Enum class’s __str__() and __repr__() соответственно; другие коды (например, %i или %h для IntEnum) рассматривают член перечисления как его смешанный тип.

  5. Formatted string literals, str.format() и format() будут использовать методы __format__() смешанного типа, если только __str__() или __format__() не переопределены в подклассе, в этом случае будут использоваться переопределенные методы или методы Enum. Используйте коды формата !s и !r для принудительного использования методов Enum класса __str__() и __repr__().

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

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

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

>>> class Coordinate(bytes, Enum):
...     """
...     Coordinate with binary codes that can be indexed by the int code.
...     """
...     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):      # this is the only change from above
...         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 = () # New color, no Pantone code yet!
...
>>> Swatch.SEA_GREEN
<Swatch.SEA_GREEN>
>>> Swatch.SEA_GREEN.pantone
'1246'
>>> Swatch.BLEACHED_CORAL.pantone
'unknown'

Примечание

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

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

TimePeriod

Пример, демонстрирующий использование атрибута _ignore_:

>>> from datetime import timedelta
>>> class Period(timedelta, Enum):
...     "different lengths of time"
...     _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)>]

Чем отличаются энумы?

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

Классы Enum

Метакласс EnumMeta отвечает за предоставление __contains__(), __dir__(), __iter__() и других методов, которые позволяют делать с классом Enum такие вещи, которые не удаются обычному классу, например list(Color) or some_enum_var in Color. EnumMeta is responsible for ensuring that various other methods on the final Enum class are correct (such as __new__(), __getnewargs__(), __str__() and __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_ – используется Functional 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_ необходим, поскольку порядок определения теряется до того, как он может быть записан.

_Private__names

Private names будут нормальными атрибутами в Python 3.11 вместо ошибки или члена (в зависимости от того, заканчивается ли имя символом подчеркивания). Использование этих имен в 3.10 приведет к выдаче DeprecationWarning.

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

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

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

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

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

Классы Enum всегда оцениваются как True.

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

Если вы дадите своему подклассу Enum дополнительные методы, как в классе Planet выше, эти методы будут отображаться в dir() члена, но не в классе:

>>> dir(Planet)
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH)
['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', '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)  # named combination
<Color.YELLOW: 3>
>>> Color(7)      # not named combination
<Color.CYAN|MAGENTA|BLUE|YELLOW|GREEN|RED: 7>

Примечание

В 3.11 неименованные комбинации флагов будут выдавать только канонические члены флагов (они же однозначные флаги). Таким образом, Color(7) будет выдавать что-то вроде <Color.BLUE|GREEN|RED: 7>.

Вернуться на верх