Перечисление 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).
Примечание
Регистр членов перечисления
Поскольку перечисления используются для представления констант и для предотвращения конфликтов имен между методами/атрибутами класса-миксина и именами перечисления, мы настоятельно рекомендуем использовать имена членов с ПРОПИСНЫМИ буквами и будем использовать этот стиль в наших примерах.
В зависимости от характера перечисления, значение члена может быть или не быть важным, но в любом случае это значение может быть использовано для получения соответствующего члена:
>>> 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) отлично подходит, если нашей переменной нужен только один день, но что, если нам нужно несколько? Возможно, мы пишем функцию для планирования домашних дел на неделю и не хотим использовать список — мы могли бы использовать другой тип перечисления (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), но не строго навязывается. В подавляющем большинстве случаев значения фактического перечисления не важны. Но если значение важно, перечисления могут иметь произвольные значения.
Перечисления являются классами 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__(), любое(ые) значение(я), заданное(ые) члену перечисления, будет передано(ы) в эти методы. См. Планета для примера.
Примечание
Метод __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.
Примечание
Добавление декоратора dataclass() к Enum и его подклассам не поддерживается. Это не вызовет никаких ошибок, но приведет к очень странным результатам во время выполнения, таким как равенство членов друг другу:
>>> @dataclass # don't do this: it does not make any sense ... class Color(Enum): ... RED = 1 ... BLUE = 2 ... >>> Color.RED is Color.BLUE False >>> Color.RED == Color.BLUE # problem is here: they should not be equal True
Сериализация
Перечисления можно сериализовать и десериализовать:
>>> 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 является False в булевом смысле, но по умолчанию все члены перечисления оцениваются как True.
Сериализация перечислений, созданных с помощью функционального API, может быть сложной, так как используются детали реализации стека фреймов, чтобы попытаться определить, в каком модуле создано перечисление (например, это не сработает, если вы используете вспомогательную функцию в отдельном модуле, а также может не работать в IronPython или Jython). Решением является явное указание имени модуля следующим образом:
>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__)
Предупреждение
Если module не предоставлен, и перечисление не может определить его, новые члены перечисления не будут сериализуемы; чтобы сохранить ошибки ближе к источнику, сериализация будет отключена.
Новый протокол сериализации 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
Последний вариант — 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, ReprEnum): # or Enum instead of ReprEnum
pass
Это демонстрирует, как можно определять производные перечисления; например, FloatEnum, которые объединяют float вместо int.
Некоторые правила:
- При наследовании от
Enum, типы объединений должны следовать перед самим классомEnumв последовательности баз, как в примереIntEnumвыше. - Типы объединений должны быть наследуемыми. Например,
boolиrangeне являются наследуемыми и приведут к ошибке при создании перечисления, если используются в качестве типа объединения. - Хотя
Enumможет содержать члены любого типа, после добавления дополнительного типа все члены должны иметь значения этого типа, например,intвыше. Это ограничение не распространяется на объединения, которые добавляют только методы и не указывают другой тип. - Когда добавляется другой тип данных, атрибут
valueне такой же, как сам член перечисления, хотя они эквивалентны и будут сравниваться как равные. data type— это объединение, которое определяет__new__()илиdataclass.- Форматирование в стиле %:
%sи%rвызывают методыEnum__str__()и__repr__()соответственно; другие коды (такие как%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, которые не будут преобразованы в члены и будут удалены из конечного класса -
_generate_next_value_()— используется для получения соответствующего значения для члена перечисления; может быть переопределена -
_add_alias_()— добавляет новое имя в качестве псевдонима к существующему члену. -
_add_value_alias_()— добавляет новое значение в качестве псевдонима к существующему члену. См. MultiValueEnum для примера.Примечание
Для стандартных классов
Enumследующее значение выбирается как наибольшее увиденное значение, увеличенное на единицу.Для классов
Flagследующее выбранное значение будет следующим наибольшим значением степени двойки.Изменено в версии 3.13: Предыдущие версии использовали последнее увиденное значение вместо наибольшего.
Добавлена в версии 3.6: _missing_, _order_, _generate_next_value_
Добавлена в версии 3.7: _ignore_
Добавлена в версии 3.13: _add_alias_, _add_value_alias_
Для синхронизации кода 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' >>> 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() вместо этого.
MultiValueEnum
Поддерживает наличие более одного значения на член:
>>> class MultiValueEnum(Enum):
... def __new__(cls, value, *values):
... self = object.__new__(cls)
... self._value_ = value
... for v in values:
... self._add_value_alias_(v)
... return self
...
>>> class DType(MultiValueEnum):
... float32 = 'f', 8
... double64 = 'd', 9
...
>>> DType('f')
<DType.float32: 'f'>
>>> DType(9)
<DType.double64: 'd'>
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
Период времени
Пример демонстрации атрибута _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.13/howto/enum.html