Spec-Zone.ru › Python 3.9

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

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

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

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

Примечание

Регистр членов перечисления

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

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

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

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

class enum.auto

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

Новое в версии 3.6: Flag, IntFlag, auto

Создание перечисления

Перечисления создаются с использованием синтаксиса 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.RED — RED, значение Color.BLUE — 3 и т. д.)

Примечание

Несмотря на то, что мы используем синтаксис 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

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

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

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

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

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

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

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

Сериализация

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

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

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

Примечание

С протоколом сериализации 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 имеет смысл в булевом смысле, но все члены перечисления оцениваются как True.

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

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__)

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

Если module не указан, и Enum не может определить, что это такое, новые члены 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)
значение

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

END_OF_DOCUMENT_MARKER

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

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 не устанавливает ни одного флага (значение 0), её логическое значение равно 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

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

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

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

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

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)>]
END_OF_DOCUMENT_MARKER

Как перечисления отличаются?

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

Классы перечислений

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

Члены перечисления (или экземпляры)

Самое интересное в членах перечисления заключается в том, что они являются синглтонами. 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_ необходим, так как порядок определения теряется до его записи.

_Приватные__имена

Приватные имена будут обычными атрибутами в Python 3.11, а не ошибкой или членом (в зависимости от того, оканчивается ли имя на нижнее подчёркивание). Использование этих имён в 3.9 и 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 члены, смешанные с типами, не являющимися Enum (например, int, str и т.д.), оцениваются по правилам смешанного типа; в противном случае все члены оцениваются как True. Чтобы сделать логическое значение вашего перечисления зависящим от значения члена, добавьте следующее в ваш класс:

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__', 'name', 'surface_gravity', 'value']

Объединение членов флага 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>.

© 2001–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.9/library/enum.html

Spec-Zone.ru

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