Enum HOWTO
An Enum is a set of symbolic names bound to unique values. They are similar to global variables, but they offer a more useful repr(), grouping, type-safety, and a few other features.
They are most useful when you have a variable that can take one of a limited selection of values. For example, the days of the week:
>>> from enum import Enum >>> class Weekday(Enum): ... MONDAY = 1 ... TUESDAY = 2 ... WEDNESDAY = 3 ... THURSDAY = 4 ... FRIDAY = 5 ... SATURDAY = 6 ... SUNDAY = 7
Or perhaps the RGB primary colors:
>>> from enum import Enum >>> class Color(Enum): ... RED = 1 ... GREEN = 2 ... BLUE = 3
As you can see, creating an Enum is as simple as writing a class that inherits from Enum itself.
Примечание
Регистр членов Enum
Поскольку Enum используются для представления констант, рекомендуется использовать имена членов в верхнем регистре, и этот стиль будет использоваться в наших примерах.
В зависимости от типа перечисления значение члена может быть или не быть важным, но в любом случае это значение может быть использовано для получения соответствующего члена:
>>> Weekday(3) <Weekday.WEDNESDAY: 3>
Как вы можете видеть, строка представления члена показывает имя перечисления, имя члена и значение. Строка отображения члена показывает только имя перечисления и имя члена:
>>> print(Weekday.THURSDAY) Weekday.THURSDAY
Тип члена перечисления — это перечисление, к которому он принадлежит:
>>> type(Weekday.MONDAY) <enum 'Weekday'> >>> isinstance(Weekday.FRIDAY, Weekday) True
У членов перечисления есть атрибут, который содержит только их name:
>>> print(Weekday.TUESDAY.name) TUESDAY
Аналогично, у них есть атрибут для их value:
>>> Weekday.WEDNESDAY.value 3
В отличие от многих языков, которые рассматривают перечисления только как пары имя/значение, Python Enum может иметь добавленное поведение. Например, datetime.date имеет два метода для возвращения дня недели: weekday() и isoweekday(). Разница в том, что один из них считает от 0 до 6, а другой — от 1 до 7. Вместо того, чтобы отслеживать это самостоятельно, мы можем добавить метод к перечислению Weekday для извлечения дня из экземпляра date и возврата соответствующего члена перечисления:
@classmethod
def from_date(cls, date):
return cls(date.isoweekday())
Полное перечисление Weekday теперь выглядит так:
>>> class Weekday(Enum): ... MONDAY = 1 ... TUESDAY = 2 ... WEDNESDAY = 3 ... THURSDAY = 4 ... FRIDAY = 5 ... SATURDAY = 6 ... SUNDAY = 7 ... # ... @classmethod ... def from_date(cls, date): ... return cls(date.isoweekday())
Теперь мы можем узнать, какой сегодня день! Обратите внимание:
>>> from datetime import date >>> Weekday.from_date(date.today()) <Weekday.TUESDAY: 2>
Конечно, если вы читаете это в другой день, вы увидите этот день вместо него.
Это перечисление Weekday отлично подходит, если нашей переменной нужен только один день, но что, если нам нужно несколько? Может быть, мы пишем функцию для планирования дел на неделю и не хотим использовать list — мы могли бы использовать другой тип Enum:
>>> from enum import Flag >>> class Weekday(Flag): ... MONDAY = 1 ... TUESDAY = 2 ... WEDNESDAY = 4 ... THURSDAY = 8 ... FRIDAY = 16 ... SATURDAY = 32 ... SUNDAY = 64
Мы изменили две вещи: мы унаследовали от Flag, и значения являются степенями двойки.
Точно так же, как и исходное перечисление Weekday выше, мы можем иметь единственный выбор:
>>> first_week_day = Weekday.MONDAY >>> first_week_day <Weekday.MONDAY: 1>
Но Flag также позволяет нам объединить несколько членов в одну переменную:
>>> weekend = Weekday.SATURDAY | Weekday.SUNDAY >>> weekend <Weekday.SATURDAY|SUNDAY: 96>
Вы даже можете перебрать переменную Flag:
>>> for day in weekend: ... print(day) Weekday.SATURDAY Weekday.SUNDAY
Хорошо, давайте создадим некоторые дела:
>>> chores_for_ethan = {
... 'feed the cat': Weekday.MONDAY | Weekday.WEDNESDAY | Weekday.FRIDAY,
... 'do the dishes': Weekday.TUESDAY | Weekday.THURSDAY,
... 'answer SO questions': Weekday.SATURDAY,
... }
И функция для отображения дел на заданный день:
>>> def show_chores(chores, day): ... for chore, days in chores.items(): ... if day in days: ... print(chore) >>> show_chores(chores_for_ethan, Weekday.SATURDAY) answer SO questions
В тех случаях, когда фактические значения членов не важны, вы можете сэкономить себе немного работы и использовать auto() для значений:
>>> from enum import auto >>> class Weekday(Flag): ... MONDAY = auto() ... TUESDAY = auto() ... WEDNESDAY = auto() ... THURSDAY = auto() ... FRIDAY = auto() ... SATURDAY = auto() ... SUNDAY = auto() ... WEEKEND = SATURDAY | SUNDAY
Программный доступ к членам перечисления и их атрибутам
Иногда полезно получить доступ к членам перечислений программно (т.е. ситуации, где 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: 'SQUARE' already defined as 2
Однако член перечисления может иметь другие имена, связанные с ним. Учитывая две записи A и B с одинаковым значением (и A определено первым), B является псевдонимом для члена A. Поиск по значению значения A вернёт члена A. Поиск по имени A вернёт члена 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>
Примечание
Попытка создать члена с таким же именем, как и уже определённый атрибут (другой член, метод и т.д.), или попытка создать атрибут с таким же именем, как и член, запрещена.
Обеспечение уникальных значений перечисления
По умолчанию перечисления разрешают несколько имён в качестве псевдонимов для одного значения. Когда это поведение нежелательно, можно использовать декоратор unique():
>>> 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() ... >>> [member.value for member in Color] [1, 2, 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() ... >>> [member.value for member in Ordinal] ['NORTH', 'SOUTH', 'EAST', 'WEST']
Примечание
Метод _generate_next_value_() должен быть определен до любых членов.
Итерация
При итерации по членам перечисления псевдонимы не предоставляются:
>>> list(Shape) [<Shape.SQUARE: 2>, <Shape.DIAMOND: 1>, <Shape.CIRCLE: 3>] >>> list(Weekday) [<Weekday.MONDAY: 1>, <Weekday.TUESDAY: 2>, <Weekday.WEDNESDAY: 4>, <Weekday.THURSDAY: 8>, <Weekday.FRIDAY: 16>, <Weekday.SATURDAY: 32>, <Weekday.SUNDAY: 64>]
Обратите внимание, что псевдонимы Shape.ALIAS_FOR_SQUARE и Weekday.WEEKEND не отображаются.
Специальный атрибут __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']
Примечание
Псевдонимы для флагов включают значения с несколькими установленными флагами, такие как 3, и без установленных флагов, т.е. 0.
Сравнения
Члены перечисления сравниваются по идентичности:
>>> 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 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 для примера.
Примечание
Метод __new__(), если определен, используется во время создания членов перечисления; затем он заменяется методом перечисления __new__(), который используется после создания класса для поиска существующих членов. См. Когда использовать __new__() вместо __init__() для получения дополнительных сведений.
Ограниченное наследование от перечислений
Новый класс Enum должен иметь один базовый класс перечисления, не более одного конкретного типа данных и столько классов миксинов на основе object, сколько необходимо. Порядок этих базовых классов:
class EnumName([mix-in, ...,] [data-type,] base-enum):
pass
Кроме того, наследование от перечисления разрешено только в том случае, если перечисление не определяет никаких членов. Поэтому это запрещено:
>>> class MoreColor(Color): ... PINK = 17 ... Traceback (most recent call last): ... TypeError: <enum 'MoreColor'> cannot extend <enum 'Color'>
Но это разрешено:
>>> class Foo(Enum): ... def some_behavior(self): ... pass ... >>> class Bar(Foo): ... HAPPY = 1 ... SAD = 2 ...
Разрешение наследования от перечислений, которые определяют члены, приведёт к нарушению некоторых важных инвариантов типов и экземпляров. С другой стороны, имеет смысл разрешить совместное использование некоторого общего поведения между группой перечислений. (См. OrderedEnum для примера.)
Выгрузка в pickle
Перечисления можно загрузить и выгрузить в pickle:
>>> from test.test_enum import Fruit >>> from pickle import dumps, loads >>> Fruit.TOMATO is loads(dumps(Fruit.TOMATO)) True
Применяются обычные ограничения для выгрузки в pickle: перечисляемые перечисления должны быть определены на верхнем уровне модуля, так как для выгрузки в pickle требуется их импорт из этого модуля.
Примечание
С помощью протокола pickle версии 4 можно легко выгрузить в pickle перечисления, вложенные в другие классы.
Можно изменить способ выгрузки/загрузки элементов перечисления в pickle, определив __reduce_ex__() в классе перечисления. По умолчанию используется способ по значению, но перечисления со сложными значениями могут использовать способ по имени:
>>> import enum >>> class MyEnum(enum.Enum): ... __reduce_ex__ = enum.pickle_by_enum_name
Примечание
Использование способа по имени для флагов не рекомендуется, так как неопределённые псевдонимы не будут загружены из pickle.
Функциональный API
Класс Enum является вызываемым, предоставляя следующий функциональный API:
>>> Animal = Enum('Animal', 'ANT BEE CAT DOG')
>>> Animal
<enum 'Animal'>
>>> Animal.ANT
<Animal.ANT: 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.
Выгрузка в pickle перечислений, созданных с помощью функционального API, может быть сложной, поскольку для определения модуля, в котором создаётся перечисление, используются детали реализации стека кадров (например, это не сработает, если вы используете вспомогательную функцию в отдельном модуле, а также может не работать в IronPython или Jython). Решение — явно указать имя модуля следующим образом:
>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__)
Предупреждение
Если module не предоставлен, и Enum не может определить, что это такое, новые члены Enum не будут выгружаемыми в pickle; чтобы сохранить ошибки ближе к источнику, выгрузка в pickle будет отключена.
Новый протокол 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,
)
- значение: то, что новый класс перечисления будет записывать как своё имя.
-
имена: члены перечисления. Это может быть строка имён, разделённая пробелами или запятыми (значения будут начинаться с 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} - модуль: имя модуля, где можно найти новый класс перечисления.
- qualname: где в модуле можно найти новый класс перечисления.
- тип: тип для добавления в новый класс перечисления.
- 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]
StrEnum
Вторая разновидность Enum, которая предоставляется, также является подклассом str. Элементы StrEnum могут сравниваться со строками; как следствие, строковые перечисления разных типов также могут сравниваться друг с другом.
Введено в версии 3.11.
IntFlag
Следующая разновидность Enum, предоставляемая, IntFlag, также основана на int. Различие заключается в том, что члены IntFlag могут объединяться с помощью побитовых операторов (&, |, ^, ~), а результат по-прежнему является членом IntFlag, если это возможно. Как и члены IntEnum, члены IntFlag также являются целыми числами и могут использоваться там, где используется int.
Примечание
Любая операция над членом IntFlag помимо побитовых операций приведёт к потере членства в IntFlag.
Побитовые операции, приводящие к недопустимым значениям IntFlag, приведут к потере членства в IntFlag. Подробности см. в FlagBoundary.
Введено в версии 3.6.
Изменено в версии 3.11.
Пример класса 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: 0> >>> Perm(7) <Perm.RWX: 7>
Примечание
Названные комбинации считаются псевдонимами. Псевдонимы не отображаются во время итерации, но могут возвращаться в результатах поиска по значению.
Изменено в версии 3.11.
Ещё одно важное отличие между IntFlag и Enum состоит в том, что если ни один флаг не установлен (значение равно 0), его булево значение равно False:
>>> Perm.R & Perm.X <Perm: 0> >>> bool(Perm.R & Perm.X) False
Поскольку члены IntFlag также являются подклассами int, их можно объединять с ними (но членство в IntFlag может быть потеряно):
>>> Perm.X | 4 <Perm.R|X: 5> >>> Perm.X + 8 9
Примечание
Оператор отрицания, ~, всегда возвращает член IntFlag с положительным значением:
>>> (~Perm.X).value == (Perm.R|Perm.W).value == 6 True
Члены IntFlag также можно перебирать:
>>> list(RW) [<Perm.R: 4>, <Perm.W: 2>]
Введено в версии 3.11.
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> >>> 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
Члены Flag также можно перебирать:
>>> purple = Color.RED | Color.BLUE >>> list(purple) [<Color.RED: 1>, <Color.BLUE: 2>]
Введено в версии 3.11.
Примечание
Для большинства нового кода настоятельно рекомендуется использовать Enum и Flag, так как IntEnum и IntFlag нарушают некоторые семантические обещания перечисления (путем сравнивания с целыми числами, и, следовательно, по транзитивности с другими несвязанными перечислениями). IntEnum и IntFlag следует использовать только в тех случаях, когда Enum и Flag не подойдут; например, при замене целочисленных констант перечислениями или для взаимодействия с другими системами.
Другие
Хотя IntEnum является частью модуля enum, его реализация была бы очень простой:
class IntEnum(int, Enum):
pass
Это демонстрирует, как можно определить похожие производные перечисления; например, FloatEnum , который использует float вместо int.
Некоторые правила:
- При наследовании от
Enum, типы включаемых свойств должны появляться перед самимEnumв последовательности базовых классов, как в примере сIntEnumвыше. - Типы включаемых свойств должны допускать наследование. Например,
boolиrangeне допускают наследования и вызовут ошибку во время создания перечисления, если будут использоваться в качестве типа включаемого свойства. - Хотя
Enumможет содержать члены любого типа, после включения дополнительного типа все члены должны иметь значения этого типа, например,intвыше. Это ограничение не относится к включаемым свойствам, которые добавляют только методы и не определяют другой тип. - Когда включается другой тип данных, атрибут
valueне такой же, как сам член перечисления, хотя он эквивалентен и сравнивается как равный. data type— это включаемое свойство, которое определяет__new__().- Форматирование с использованием %-символа:
%sи%rвызывают методы__str__()и__repr__()классаEnumсоответственно; другие коды (например,%iили%hдля IntEnum) обрабатывают член перечисления как его тип, добавленный с помощью включаемого свойства. -
Форматированные строковые литералы,
str.format()иformat()будут использовать метод__str__()перечисления.
Когда использовать __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
Предупреждение
Не вызывайте super().__new__(), так как ищется только __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для получения соответствующего значения для члена перечисления; может быть переопределена
Примечание
Для стандартных классов Enum выбирается следующее значение — последнее увиденное значение, увеличенное на единицу.
Для классов Flag выбирается следующее наибольшее значение степени двойки, независимо от последнего увиденного значения.
Новое в версии 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_: ['RED', 'BLUE', 'GREEN'] ['RED', 'GREEN', 'BLUE']
Примечание
В коде Python 2 атрибут _order_ необходим, так как порядок определения теряется до его записи.
_Приватные_имена
Приватные имена не преобразуются в члены перечисления, а остаются обычными атрибутами.
Изменено в версии 3.11.
Enum тип члена
Члены перечисления являются экземплярами класса перечисления и обычно доступны как EnumClass.member. В некоторых ситуациях, таких как написание пользовательского поведения перечисления, полезно напрямую получить доступ к одному члену из другого, и эта возможность поддерживается.
Изменено в версии 3.5.
Создание членов, смешанных с другими типами данных
При наследовании других типов данных, таких как int или str, с Enum, все значения после = передаются в конструктор этого типа данных. Например:
>>> class MyEnum(IntEnum): # help(int) -> int(x, base=10) -> integer ... example = '11', 16 # so x='11' and base=16 ... >>> MyEnum.example.value # and hex(11) is... 17
Логическое значение 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__', 'mass', 'name', 'radius', 'surface_gravity', 'value']
Объединение членов Flag
При итерации по сочетанию членов Flag будут возвращены только члены, состоящие из одного бита:
>>> 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.RED|GREEN|BLUE: 7>
Flag и IntFlag детали
Используя следующий фрагмент для наших примеров:
>>> class Color(IntFlag): ... BLACK = 0 ... RED = 1 ... GREEN = 2 ... BLUE = 4 ... PURPLE = RED | BLUE ... WHITE = RED | GREEN | BLUE ...
следующие утверждения верны:
- однобитные флаги являются каноническими
- многобитные и нулевые флаги являются псевдонимами
-
при итерации возвращаются только канонические флаги:
>>> list(Color.WHITE) [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
-
отрицание флага или набора флагов возвращает новый флаг/набор флагов с соответствующим положительным целочисленным значением:
>>> Color.BLUE <Color.BLUE: 4> >>> ~Color.BLUE <Color.RED|GREEN: 3>
-
имена псевдофлагов строятся из имён их членов:
>>> (Color.RED | Color.GREEN).name 'RED|GREEN'
-
многобитные флаги, то есть псевдонимы, могут быть возвращены операциями:
>>> Color.RED | Color.BLUE <Color.PURPLE: 5> >>> Color(7) # or Color(-1) <Color.WHITE: 7> >>> Color(0) <Color.BLACK: 0>
-
проверка принадлежности/содержания: нулевые флаги всегда считаются содержащимися:
>>> Color.BLACK in Color.WHITE True
в противном случае, только если все биты одного флага находятся в другом флаге, будет возвращено True:
>>> Color.PURPLE in Color.WHITE True >>> Color.GREEN in Color.PURPLE False
Существует новый механизм граничного контроля, регулирующий обработку значений за пределами диапазона/недействительных битов: STRICT, CONFORM, EJECT, и KEEP:
- STRICT –> вызывает исключение при обнаружении недопустимых значений
- CONFORM –> отбрасывает все недопустимые биты
- EJECT –> теряет статус флага и становится обычным целым числом с заданным значением
-
KEEP –> сохраняет дополнительные биты
- сохраняет статус флага и дополнительные биты
- дополнительные биты не отображаются при итерации
- дополнительные биты отображаются в repr() и str()
По умолчанию для Flag используется STRICT, по умолчанию для IntFlag — EJECT, а по умолчанию для _convert_ — KEEP (см. ssl.Options для примера, когда нужно KEEP).
В чём разница между перечислениями и флагами?
Перечисления имеют специальный метакласс, влияющий на многие аспекты как производных классов Enum, так и их экземпляров (членов).
Классы перечислений
Метакласс EnumType отвечает за предоставление __contains__(), __dir__(), __iter__() и других методов, которые позволяют выполнять действия с классом Enum, которые невозможны с обычным классом, например, list(Color) или some_enum_var in Color. EnumType отвечает за обеспечение корректности различных других методов конечного класса Enum (таких как __new__(), __getnewargs__(), __str__() и __repr__()).
Классы флагов
Флаги имеют расширенный взгляд на алиасинг: чтобы быть каноническим, значение флага должно быть степенью двойки, и имя не должно повторяться. Таким образом, помимо определения алиаса в Enum, флаг без значения (также известный как 0) или с несколькими значениями, являющимися степенями двойки (например, 3) считается алиасом.
Члены перечисления (также известные как экземпляры)
Самое интересное в членах перечисления заключается в том, что они являются синглтонами. EnumType создаёт их все во время создания самого класса перечисления и затем устанавливает специальную функцию, гарантирующую, что новые экземпляры никогда не будут созданы, возвращая только существующие члены.
Члены флагов
Члены флагов можно перебирать так же, как и класс Flag, и будут возвращены только канонические члены. Например:
>>> list(Color) [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
(Обратите внимание, что BLACK, PURPLE, и WHITE не отображаются.)
Инвертирование члена флага возвращает соответствующее положительное значение, а не отрицательное — например:
>>> ~Color.RED <Color.GREEN|BLUE: 6>
Члены флагов имеют длину, соответствующую количеству значений, являющихся степенями двойки. Например:
>>> len(Color.PURPLE) 2
Кулинарная книга по перечислениям
Хотя Enum, IntEnum, StrEnum, Flag и IntFlag предназначены для решения большинства задач, они не могут покрыть все. Вот рецепты для некоторых разных типов перечислений, которые можно использовать непосредственно или в качестве примеров для создания собственных.
Пропуск значений
Во многих случаях не имеет значения, каким является фактическое значение перечисления. Существует несколько способов определения этого типа простого перечисления:
- использовать экземпляры
autoдля значения - использовать экземпляры
objectв качестве значения - использовать описательный строковый текст в качестве значения
- использовать кортеж в качестве значения и пользовательскую
__new__()для замены кортежа значениемint
Использование любого из этих методов указывает пользователю, что эти значения не важны, а также позволяет добавлять, удалять или переупорядочивать элементы без необходимости перечисления оставшихся элементов.
Использование auto
Использование auto будет выглядеть так:
>>> class Color(Enum): ... RED = auto() ... BLUE = auto() ... GREEN = auto() ... >>> Color.GREEN <Color.GREEN: 3>
Использование object
Использование object будет выглядеть так:
>>> class Color(Enum): ... RED = object() ... GREEN = object() ... BLUE = object() ... >>> Color.GREEN <Color.GREEN: <object object at 0x...>>
Это также хороший пример того, почему вам может понадобиться написать свою собственную __repr__():
>>> class Color(Enum): ... RED = object() ... GREEN = object() ... BLUE = object() ... def __repr__(self): ... return "<%s.%s>" % (self.__class__.__name__, self._name_) ... >>> Color.GREEN <Color.GREEN>
Использование описательной строки
Использование строки в качестве значения будет выглядеть так:
>>> class Color(Enum): ... RED = 'stop' ... GREEN = 'go' ... BLUE = 'too fast!' ... >>> Color.GREEN <Color.GREEN: 'go'>
Использование пользовательской __new__()
Использование автоинкремента __new__() будет выглядеть так:
>>> class AutoNumber(Enum): ... 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: 2>
Чтобы сделать более универсальную AutoNumber, добавьте *args в сигнатуру:
>>> class AutoNumber(Enum): ... def __new__(cls, *args): # this is the only change from above ... value = len(cls.__members__) + 1 ... obj = object.__new__(cls) ... obj._value_ = value ... return obj ...
Затем, когда вы наследуете от AutoNumber, вы можете написать свою собственную __init__ для обработки любых дополнительных аргументов:
>>> class Swatch(AutoNumber): ... def __init__(self, pantone='unknown'): ... self.pantone = pantone ... AUBURN = '3497' ... SEA_GREEN = '1246' ... BLEACHED_CORAL = () # New color, no Pantone code yet! ... >>> Swatch.SEA_GREEN <Swatch.SEA_GREEN: 2> >>> Swatch.SEA_GREEN.pantone '1246' >>> Swatch.BLEACHED_CORAL.pantone 'unknown'
Примечание
Метод __new__(), если он определён, используется во время создания элементов перечисления; затем он заменяется методом перечисления __new__(), который используется после создания класса для поиска существующих элементов.
Предупреждение
Не вызывайте super().__new__(), так как поиск-только __new__ будет найден; вместо этого используйте тип данных напрямую — например:
obj = int.__new__(cls, value)
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
Период времени
Пример использования атрибута _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)>]
Наследование от EnumType
Хотя большинство потребностей в перечислениях могут быть удовлетворены с помощью настраиваемых подклассов Enum, либо с помощью декораторов класса, либо с помощью пользовательских функций, можно наследоваться от EnumType для предоставления другого опыта работы с перечислениями.
© 2001–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.11/howto/enum.html