Содержание страницы
HOWTO по перечислениям¶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.
Примечание
Регистр имён членов перечисления
Поскольку перечисления используются для представления констант, и чтобы избежать проблем с конфликтами имён между методами/атрибутами примесных классов и именами перечислений, мы настоятельно рекомендуем использовать имена в ВЕРХНЕМ_РЕГИСТРЕ для членов и будем использовать этот стиль в наших примерах.
В зависимости от природы перечисления значение члена может быть важным или нет, но в любом случае это значение можно использовать для получения соответствующего члена:
>>> 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())
Теперь мы можем узнать, какой сегодня день! Смотрите:
>>> import datetime as dt
>>> Weekday.from_date(dt.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
Программный доступ к членам перечисления и их атрибутам¶Programmatic access to enumeration members and their attributes
Иногда бывает полезно обращаться к членам перечисления программно (например,
в ситуациях, когда 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
Дублирование членов и значений перечисления¶Duplicating enum members and values
Наличие двух членов перечисления с одинаковым именем недопустимо:
>>> 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>
Примечание
Попытка создать член с тем же именем, что и уже определённый атрибут (другой член, метод и т.д.), или попытка создать атрибут с тем же именем, что и член, не допускается.
Обеспечение уникальности значений перечисления¶Ensuring unique enumeration values
По умолчанию перечисления допускают несколько имён в качестве псевдонимов одного и того же значения.
Если такое поведение нежелательно, можно использовать декоратор 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
Использование автоматических значений¶Using automatic values
Если точное значение неважно, можно использовать 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_() должен быть определён до любых членов.
Перебор¶Iteration
Перебор членов перечисления не включает псевдонимы:
>>> 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.
Сравнения¶Comparisons
Члены перечисления сравниваются по идентичности:
>>> 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
Предупреждение
Можно перезагружать модули – если перезагруженный модуль содержит перечисления, они будут воссозданы, и новые члены могут не совпадать (по идентичности или равенству) с исходными членами.
Допустимые члены и атрибуты перечислений¶Allowed members and attributes of enumerations
Большинство примеров выше используют целые числа для значений перечисления. Использование целых чисел кратко и удобно (и предоставляется по умолчанию функциональным API), но не является строго обязательным. В подавляющем большинстве случаев неважно, каково фактическое значение перечисления. Но если значение важно, перечисления могут иметь произвольные значения.
Перечисления – это классы Python и могут иметь обычные методы и специальные методы. Если есть такое перечисление:
>>> class Mood(Enum):
... FUNKY = 1
... HAPPY = 3
...
... def describe(self):
... # self – это член перечисления
... return self.name, self.value
...
... def __str__(self):
... return 'my custom str! {0}'.format(self.value)
...
... @classmethod
... def favorite_mood(cls):
... # cls здесь – это перечисление
... 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__() vs. __init__().
Ограниченное создание подклассов Enum¶Restricted Enum subclassing
Новый класс 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 support
При наследовании от 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 # не делайте так: это не имеет смысла
... class Color(Enum):
... RED = 1
... BLUE = 2
...
>>> Color.RED is Color.BLUE
False
>>> Color.RED == Color.BLUE # проблема здесь: они не должны быть равны
True
Пиклинг¶Pickling
Перечисления могут быть упакованы и распакованы с помощью модуля 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__() в классе перечисления. Метод по умолчанию – по значению (by-value), но перечисления со сложными значениями могут захотеть использовать по имени (by-name):
>>> import enum
>>> class MyEnum(enum.Enum):
... __reduce_ex__ = enum.pickle_by_enum_name
Примечание
Использование по имени для флагов не рекомендуется, так как безымянные псевдонимы не будут распаковываться.
Функциональное API¶Functional 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.
Pickling перечислений, созданных с помощью функционального API, может быть сложным, так как используются детали реализации стека фреймов для определения модуля, в котором создаётся перечисление (например, это не сработает, если используется вспомогательная функция в отдельном модуле, и может не работать на IronPython или Jython). Решение – явно указать имя модуля следующим образом:
>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__)
Предупреждение
Если module не указан, и Enum не может его определить, новые члены Enum не будут поддерживать распаковку; чтобы ошибки были ближе к источнику, pickling будет отключён.
Новый протокол 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.
Производные перечисления¶Derived Enumerations
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.12.
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.12.
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.12.
Примечание
Для большинства нового кода настоятельно рекомендуются Enum и Flag, поскольку IntEnum и IntFlag нарушают некоторые семантические обещания перечисления (будучи сравнимыми с целыми числами, а значит, по транзитивности, и с другими не связанными перечислениями). IntEnum и IntFlag следует использовать только в тех случаях, когда Enum и Flag не подходят; например, при замене целочисленных констант на перечисления или для взаимодействия с другими системами.
Другие¶Others
Хотя IntEnum является частью модуля enum, его очень просто реализовать самостоятельно:
class IntEnum(int, ReprEnum): # или Enum вместо ReprEnum
pass
Это показывает, как можно определить подобные производные перечисления; например, FloatEnum, в который примешивается float вместо int.
Некоторые правила:
При наследовании от
Enumтипы, примешиваемые (mix-in), должны быть указаны перед самим классомEnumв списке базовых классов, как в примереIntEnumвыше.Примешиваемые типы должны быть наследуемыми. Например,
boolиrangeне являются наследуемыми и приведут к ошибке во время создания Enum, если используются в качестве примешиваемого типа.Хотя
Enumможет содержать члены любого типа, после примешивания дополнительного типа все члены должны иметь значения этого типа, напримерintвыше. Это ограничение не распространяется на примеси, которые только добавляют методы и не задают другой тип.При примешивании другого типа данных атрибут
valueотличается от самого члена перечисления, хотя он эквивалентен и будет равным при сравнении.data type– это примесь (mixin), которая определяет__new__(), илиdataclassФорматирование в стиле %:
%sи%rвызывают__str__()и__repr__()классаEnumсоответственно; другие коды (например,%iили%hдля IntEnum) обрабатывают член перечисления как его примешанный тип.Форматированные строковые литералы,
str.format()иformat()будут использовать метод__str__()перечисления.
Когда использовать __new__() или __init__()¶When to use __new__() vs. __init__()
__new__() следует использовать всякий раз, когда требуется настроить фактическое значение члена Enum. Любые другие изменения можно вносить как в __new__(), так и в __init__(), при этом предпочтение отдаётся __init__().
Например, если требуется передать конструктору несколько элементов, но только один из них должен быть значением:
>>> class Coordinate(bytes, Enum):
... """
... Согласование с двоичными кодами, которые можно индексировать по целочисленному коду.
... """
... 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__, предназначенный только для поиска; вместо этого используйте непосредственно тип данных.
Тонкие моменты¶Finer Points
Поддерживаемые имена __dunder__ и _sunder_¶Supported __dunder__ and _sunder_ names
Поддерживаемые имена __dunder__ и _sunder_ можно найти в документации API Enum.
_Private__names¶
Приватные имена не преобразуются в члены перечисления, а остаются обычными атрибутами.
Изменено в версии 3.11.
Тип члена Enum¶Enum member type
Члены перечисления являются экземплярами своего класса перечисления и обычно доступны как EnumClass.member. В определённых ситуациях, например, при написании пользовательского поведения перечисления, возможность прямого обращения одного члена к другому бывает полезна и поддерживается; однако во избежание конфликтов имён между именами членов и атрибутами/методами из примешанных классов настоятельно рекомендуется использовать имена в верхнем регистре.
Изменено в версии 3.5.
Создание членов, примешанных к другим типам данных¶Creating members that are mixed with other data types
При наследовании от других типов данных, таких как int или str, в сочетании с Enum, все значения после = передаются конструктору этого типа данных. Например:
>>> class MyEnum(IntEnum): # help(int) -> int(x, base=10) -> integer
... example = '11', 16 # итак, x='11' и base=16
...
>>> MyEnum.example.value # и hex(11) – это...
17
Логическое значение классов и членов Enum¶Boolean value of Enum classes and members
Классы перечислений, в которые примешаны не-Enum типы (например, int, str и т.д.), вычисляются по правилам примешанного типа; в противном случае все члены вычисляются как True. Чтобы логическое значение вашего перечисления зависело от значения члена, добавьте в свой класс следующее:
def __bool__(self):
return bool(self.value)
Enum классы с методами¶Enum classes with methods
Если добавить в подкласс перечисления дополнительные методы, например, как в классе 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¶Combining members of Flag
Итерация по комбинации элементов Flag вернёт только те элементы, которые
состоят из одного бита:
>>> class Color(Flag):
... RED = auto()
... GREEN = auto()
... BLUE = auto()
... MAGENTA = RED | BLUE
... YELLOW = RED | GREEN
... CYAN = GREEN | BLUE
...
>>> Color(3) # именованная комбинация
<Color.YELLOW: 3>
>>> Color(7) # не именованная комбинация
<Color.RED|GREEN|BLUE: 7>
Flag и IntFlag мелочи¶Flag and IntFlag minutia
Используя следующий фрагмент для примеров:
>>> 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 # фактически Perm(0) True
многобитовые флаги, также известные как псевдонимы, могут возвращаться в результате операций:
>>> Color.RED | Color.BLUE <Color.PURPLE: 5> >>> Color(7) # или 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 (см. ssl.Options для
примера, когда требуется KEEP).
Чем отличаются перечисления и флаги?¶How are Enums and Flags different?
Перечисления имеют собственный метакласс, который влияет на многие аспекты как производных классов Enum,
так и их экземпляров (элементов).
Классы Enum¶Enum Classes
Метакласс EnumType отвечает за предоставление методов
__contains__(), __dir__(), __iter__() и других, которые
позволяют выполнять с классом Enum действия, невозможные для обычного
класса, например, list(Color) или some_enum_var in Color. EnumType отвечает
за обеспечение корректности различных других методов итогового класса Enum
(таких как __new__(), __getnewargs__(),
__str__() и __repr__()).
Классы Flag¶Flag Classes
Флаги имеют расширенное представление о псевдонимах: чтобы быть каноническим, значение флага
должно быть степенью двойки и не быть дублирующимся именем. Таким образом, в дополнение к
определению псевдонима Enum, флаг без значения (также известный как 0) или с более чем одним
значением, являющимся степенью двойки (например, 3), считается псевдонимом.
Элементы Enum (также известные как экземпляры)¶Enum Members (aka instances)
Самое интересное в членах перечисления – то, что они синглтоны.
EnumType создаёт их все при создании самого класса перечисления,
а затем подставляет собственный __new__(), чтобы гарантировать, что новые экземпляры
никогда не будут созданы, возвращая только существующие.
Члены флага¶Flag Members
Члены флага можно перебирать так же, как и класс 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¶Enum Cookbook
Хотя Enum, IntEnum, StrEnum, Flag и IntFlag призваны покрывать большинство сценариев использования, они не могут охватить их все. Здесь приведены рецепты для некоторых различных типов перечислений, которые можно использовать напрямую или в качестве примеров для создания собственных.
Пропуск значений¶Omitting values
Во многих сценариях пользователю всё равно, каково фактическое значение перечисления. Существует несколько способов определить такой простой тип перечисления:
использовать экземпляры
autoв качестве значенияиспользовать экземпляры
objectв качестве значенияиспользовать описательную строку в качестве значения
использовать кортеж в качестве значения и собственный
__new__()для замены кортежа на значениеint
Использование любого из этих методов показывает пользователю, что эти значения не важны, а также позволяет добавлять, удалять или переупорядочивать члены без необходимости перенумерации остальных членов.
Использование auto¶Using auto
Использование auto будет выглядеть так:
>>> class Color(Enum):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> Color.GREEN
<Color.GREEN: 3>
Использование object¶Using 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>
Использование описательной строки¶Using a descriptive string
Использование строки в качестве значения будет выглядеть так:
>>> class Color(Enum):
... RED = 'stop'
... GREEN = 'go'
... BLUE = 'too fast!'
...
>>> Color.GREEN
<Color.GREEN: 'go'>
Использование собственного __new__()¶Using a custom __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): # это единственное изменение по сравнению с предыдущим
... 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 = () # Новый цвет, кода Pantone пока нет!
...
>>> Swatch.SEA_GREEN
<Swatch.SEA_GREEN: 2>
>>> Swatch.SEA_GREEN.pantone
'1246'
>>> Swatch.BLEACHED_CORAL.pantone
'unknown'
Примечание
Метод __new__(), если он определён, используется во время создания членов Enum; затем он заменяется методом __new__() класса Enum, который используется после создания класса для поиска существующих членов.
Предупреждение
Не вызывайте 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 # в килограммах
... self.radius = radius # в метрах
... @property
... def surface_gravity(self):
... # гравитационная постоянная (м3 кг-1 с-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_:
>>> import datetime as dt
>>> class Period(dt.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¶Subclassing EnumType
Хотя большинство потребностей в перечислениях можно удовлетворить, настраивая подклассы Enum
с помощью декораторов классов или пользовательских функций, EnumType можно
субклассировать для получения другого опыта работы с Enum.