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() -
Декоратор класса Enum, который гарантирует, что только одно имя связано с одним значением.
-
class enum.auto -
Экземпляры заменяются соответствующим значением для членов Enum. Начальное значение начинается с 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
Разрешенные члены и атрибуты перечислений
В приведенных выше примерах для значений перечисления используются целые числа. Использование целых чисел кратко и удобно (и по умолчанию предоставляется функциональным API Функциональным 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 в булевом смысле равно False, но все члены перечисления оцениваются как 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)
- 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. Как и элементы 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вызывают соответственно методыEnum__str__()и__repr__(); другие коды (например,%iили%hдля IntEnum) обрабатывают элемент перечисления как тип, добавленный посредством смешивания. - Литералы форматированных строк,
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'
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)>]
Как перечисления отличаются?
Перечисления имеют пользовательский метакласс, который влияет на многие аспекты как производных классов перечислений, так и их экземпляров (членов).
Классы перечислений
Метакласс 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_ необходим, так как порядок определения теряется до его записи.
Тип члена перечисления
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 (такими как int, str и т.д.), оцениваются по правилам смешанного типа; в противном случае все члены оцениваются как True. Для настройки булевой оценки своего перечисления в зависимости от значения члена добавьте следующее в свой класс:
def __bool__(self):
return bool(self.value)
Классы перечисления с методами
Если вы добавите дополнительные методы в подкласс 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>
© 2001–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.8/library/enum.html