Краткое руководство по Enum
Объект 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.
Примечание
Регистр имён членов 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 отлично подходит, если нашей переменной нужен только один день, но что, если требуется несколько? Допустим, мы пишем функцию для отображения домашних дел на неделю и не хотим использовать 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
Упорядоченные сравнения значений перечисления не поддерживаются. Члены 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'
Правила допустимых объявлений таковы: имена, начинающиеся и заканчивающиеся одним подчёркиванием, зарезервированы для enum и использоваться не могут; все остальные атрибуты, определённые внутри перечисления, становятся его членами, за исключением специальных методов (__str__(), __add__() и т. д.), дескрипторов (методы также являются дескрипторами) и имён переменных, перечисленных в _ignore_.
Примечание: если в перечислении определены __new__() и/или __init__(), все значения, заданные члену перечисления, будут переданы этим методам. Пример см. в разделе Planet.
Примечание
Метод __new__(), если он определён, используется при создании членов Enum; затем он заменяется методом __new__() класса Enum, который используется после создания класса для поиска существующих членов. Подробнее см. раздел Когда использовать __new__(), а когда __init__().
Ограничения на создание подклассов Enum
Новый класс 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, но не его имя.
Сериализация с помощью pickle
Перечисления можно сериализовать и десериализовать с помощью pickle:
>>> from test.test_enum import Fruit >>> from pickle import dumps, loads >>> Fruit.TOMATO is loads(dumps(Fruit.TOMATO)) True
Применяются обычные ограничения pickle: сериализуемые перечисления должны быть определены на верхнем уровне модуля, поскольку для десериализации их необходимо импортировать из этого модуля.
Примечание
Протокол pickle версии 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 не указан и Enum не удаётся определить его значение, новые члены Enum нельзя будет десериализовать; чтобы ошибка возникала ближе к месту её причины, сериализация с помощью 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,
)
- value: имя, которое будет присвоено новому классу перечисления.
-
names: члены перечисления. Это может быть строка с разделёнными пробелами или запятыми именами (если не указано иное, значения начинаются с 1):
'RED GREEN BLUE' | 'RED,GREEN,BLUE' | 'RED, GREEN, BLUE'
или итератор имён:
['RED', 'GREEN', 'BLUE']
или итератор пар (имя, значение):
[('CYAN', 4), ('MAGENTA', 5), ('YELLOW', 6)]или отображение:
{'CHARTREUSE': 7, 'SEA_GREEN': 11, 'ROSEMARY': 42} - module: имя модуля, в котором находится новый класс перечисления.
- qualname: место в модуле, где находится новый класс перечисления.
- type: тип, который нужно добавить в новый класс перечисления как миксин.
- 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 возникнет ошибка, если использовать их как тип-примесь. - Хотя
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 –> утрачивает статус Flag и становится обычным int с заданным значением
-
KEEP –> сохраняет дополнительные биты
- сохраняет статус Flag и дополнительные биты
- дополнительные биты не отображаются при итерации
- дополнительные биты отображаются в repr() и str()
Для Flag по умолчанию используется STRICT, для IntFlag — EJECT, а для _convert_ — KEEP (пример ситуации, в которой требуется KEEP, см. в ssl.Options).
Чем отличаются перечисления и флаги?
Перечисления имеют специальный метакласс, который влияет на многие аспекты как производных классов Enum, так и их экземпляров (элементов).
Классы Enum
Метакласс EnumType отвечает за предоставление методов __contains__(), __dir__(), __iter__() и других методов, позволяющих выполнять с классом Enum действия, невозможные для обычного класса, например list(Color) или some_enum_var in Color. Метакласс EnumType также отвечает за корректную работу различных методов итогового класса Enum (например, __new__(), __getnewargs__(), __str__() и __repr__()).
Классы Flag
В понятии псевдонимов для флагов применяется расширенный подход: чтобы быть каноническим, значение флага должно быть степенью двойки и не должно дублировать имя. Поэтому, помимо определения псевдонима для Enum, псевдонимом считается также флаг без значения (то есть 0) или со значением, представляющим собой сумму нескольких степеней двойки (например, 3).
Элементы Enum (то есть экземпляры)
Самое интересное в элементах перечисления — то, что они являются одиночками. Метакласс EnumType создаёт их все при создании самого класса перечисления, а затем устанавливает специальный метод __new__(), который гарантирует, что новые элементы не будут создаваться, возвращая только существующие экземпляры элементов.
Элементы Flag
Элементы флагов можно перебирать так же, как класс 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__(), если он определён, используется при создании элементов Enum; затем он заменяется методом Enum __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().
Planet
Если определён __new__() или __init__(), значение элемента перечисления будет передано этим методам:
>>> class Planet(Enum): ... MERCURY = (3.303e+23, 2.4397e6) ... VENUS = (4.869e+24, 6.0518e6) ... EARTH = (5.976e+24, 6.37814e6) ... MARS = (6.421e+23, 3.3972e6) ... JUPITER = (1.9e+27, 7.1492e7) ... SATURN = (5.688e+26, 6.0268e7) ... URANUS = (8.686e+25, 2.5559e7) ... NEPTUNE = (1.024e+26, 2.4746e7) ... def __init__(self, mass, radius): ... self.mass = mass # in kilograms ... self.radius = radius # in meters ... @property ... def surface_gravity(self): ... # universal gravitational constant (m3 kg-1 s-2) ... G = 6.67300E-11 ... return G * self.mass / (self.radius * self.radius) ... >>> Planet.EARTH.value (5.976e+24, 6378140.0) >>> Planet.EARTH.surface_gravity 9.802652743337129
TimePeriod
Пример использования атрибута _ignore_:
>>> from datetime import timedelta >>> class Period(timedelta, Enum): ... "different lengths of time" ... _ignore_ = 'Period i' ... Period = vars() ... for i in range(367): ... Period['day_%d' % i] = i ... >>> list(Period)[:2] [<Period.day_0: datetime.timedelta(0)>, <Period.day_1: datetime.timedelta(days=1)>] >>> list(Period)[-2:] [<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>]
Создание подклассов EnumType
Хотя большинство потребностей в перечислениях можно удовлетворить, настраивая подклассы Enum с помощью декораторов классов или пользовательских функций, класс EnumType можно наследовать, чтобы получить другой опыт работы с Enum.
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/howto/enum.html