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
.
Некоторые правила:
При подклассификации
Enum
смешиваемые типы должны появляться перед самимEnum
в последовательности баз, как в примереIntEnum
выше.Хотя
Enum
может иметь члены любого типа, как только вы добавляете дополнительный тип, все члены должны иметь значения этого типа, например,int
выше. Это ограничение не относится к смешениям, которые добавляют только методы и не указывают другой тип.Когда примешивается другой тип данных, атрибут
value
является не тем же самым, что и сам член перечисления, хотя он эквивалентен и будет сравниваться одинаково.Форматирование в стиле %: %s and %r call the
Enum
class’s__str__()
and__repr__()
соответственно; другие коды (например, %i или %h для IntEnum) рассматривают член перечисления как его смешанный тип.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
классы с методами¶
Если вы дадите своему подклассу 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>
.