enum
— Поддержка перечислений
Добавлено в версии 3.4.
Перечисление — это множество символических имён (полей), привязанных к уникальным постоянным значениям. Внутри перечисления поля могут сравниваться по идентичности, а само перечисление может итерироваться.
Примечание
Регистр полей 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.RED
—RED
, значение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
Следующий предоставленный вариант 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
Последний вариант — 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
.
Некоторые правила:
- При создании подкласса
Enum
типы миксины должны появляться перед самимEnum
в последовательности баз, как в примереIntEnum
выше. - Хотя у
Enum
могут быть поля любого типа, после добавления дополнительного типа у всех полей должны быть значения этого типа, напримерint
выше. Это ограничение не распространяется на миксины, которые только добавляют методы и не указывают другой тип данных, напримерint
илиstr
. - При смешивании другого типа данных атрибут
value
не совпадает с самим полем перечисления, хотя он эквивалентен и будет сравниваться равным. - %-стиль форматирования: % s и`% r` вызывают
__str__()
и__repr__()
классаEnum
соответственно; другие коды (например, %i или %h для IntEnum) рассматривают поле перечисления как его тип миксин. - Отформатированные строковые литералы,
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'
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
классы с методами
Если вы предоставите своему подклассу 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>