Enum HOWTO
Перечисление (Enum) — это набор символьных имён, связанных с уникальными значениями. Они похожи на глобальные переменные, но предлагают более удобный repr(), группировку, проверку типов и несколько других функций.
Они наиболее полезны, когда переменная может принимать одно из ограниченного набора значений. Например, дни недели:
>>> from enum import Enum >>> class Weekday(Enum): ... MONDAY = 1 ... TUESDAY = 2 ... WEDNESDAY = 3 ... THURSDAY = 4 ... FRIDAY = 5 ... SATURDAY = 6 ... SUNDAY = 7
Или, возможно, основные цвета RGB:
>>> from enum import Enum >>> class Color(Enum): ... RED = 1 ... GREEN = 2 ... BLUE = 3
Как вы видите, создание перечисления так же просто, как написание класса, который наследуется от Enum самого по себе.
Примечание
Регистр членов Enum
Поскольку перечисления используются для представления констант и для предотвращения проблем с конфликтами имён между методами/атрибутами класса-миксина и именами перечисления, мы настоятельно рекомендуем использовать имена членов в верхнем регистре (UPPER_CASE) и будем использовать этот стиль в наших примерах.
В зависимости от природы перечисления значение члена может или не может быть важным, но в любом случае это значение может быть использовано для получения соответствующего члена:
>>> Weekday(3) <Weekday.WEDNESDAY: 3>
Как вы видите, строковое представление (repr()) члена отображает имя перечисления, имя члена и значение. Представление (str()) члена отображает только имя перечисления и имя члена:
>>> 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 могут иметь добавленное поведение. Например, 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): ... @staticmethod ... 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
Упорядоченные сравнения между значениями перечисления не поддерживаются. Члены перечисления не являются целыми числами (но см. 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 для примера.
Примечание
Метод __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 для примера.)
Поддержка dataclass
При наследовании от dataclass, метод __repr__() опускает имя унаследованного класса. Например:
>>> from dataclasses import dataclass, field >>> @dataclass ... class CreatureDataMixin: ... size: str ... legs: int ... tail: bool = field(repr=False, default=True) ... >>> class Creature(CreatureDataMixin, Enum): ... BEETLE = 'small', 6 ... DOG = 'medium', 4 ... >>> Creature.DOG <Creature.DOG: size='medium', legs=4>
Используйте аргумент dataclass() repr=False для использования стандартного метода repr().
Изменено в версии 3.12: В области значения отображаются только поля dataclass, а не имя dataclass.
Сериализация
Перечисления можно сериализовать и десериализовать:
>>> from test.test_enum import Fruit >>> from pickle import dumps, loads >>> Fruit.TOMATO is loads(dumps(Fruit.TOMATO)) True
Применяются обычные ограничения для сериализации: сериализуемые перечисления должны быть определены в верхнем уровне модуля, так как десериализация требует их импортирования из этого модуля.
Примечание
С протоколом сериализации 4 легко сериализовать перечисления, вложенные в другие классы.
Можно изменить способ сериализации/десериализации элементов перечисления, определив __reduce_ex__() в классе перечисления. По умолчанию используется метод по значению, но перечисления со сложными значениями могут использовать метод по имени:
>>> import enum >>> class MyEnum(enum.Enum): ... __reduce_ex__ = enum.pickle_by_enum_name
Примечание
Использование метода по имени для флагов не рекомендуется, так как безымянные псевдонимы не будут десериализованы.
Функциональный 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 в булевом смысле, но по умолчанию все элементы перечисления оцениваются как 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,
)
- значение: то, что новый класс перечисления будет записывать как своё имя.
-
имена: элементы перечисления. Это может быть строка, разделённая пробелами или запятыми (значения будут начинаться с 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: где в модуле находится новый класс перечисления.
- тип: тип, который необходимо добавить в новый класс перечисления.
- начало: число, с которого нужно начать отсчёт, если переданы только имена.
Изменено в версии 3.5: Добавлен параметр начало.
Производные перечисления
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. Как и члены 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__(), илиdataclass- Форматирование с использованием %-символа:
%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_ необходим, так как порядок определения теряется до того, как он может быть записан.
_Private__names
Приватные имена не преобразуются в члены перечисления, а остаются обычными атрибутами.
Изменено в версии 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' >>> class Perm(IntFlag): ... R = 4 ... W = 2 ... X = 1 ... >>> (Perm.R & Perm.W).name is None # effectively Perm(0) True
-
многобитные флаги, то есть псевдонимы, могут возвращаться в результате операций:
>>> 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 создает их все во время создания самого класса перечисления и затем устанавливает пользовательский __new__() для того, чтобы предотвратить создание новых экземпляров, возвращая только существующие экземпляры элементов.
Элементы флагов
Элементы флагов можно перебирать так же, как и класс 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–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/howto/enum.html