Python enum HOWTO Печать
Добавил(а) microsin   

Класс Enum в Python [2] это набор символических имен, привязанных к уникальным значениям. Это подобно глобальным переменным, но также предоставляются repr(), группирование, type-safety и другие удобные фичи.

Значения перечисления больше всего подходят для случая, когда переменная может иметь ограниченный набор выбираемых значений. Например, это могут быть дни недели:

>>> 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() элемента перечисления покажет имя enum, имя члена enum и значение члена enum. Функция str() элемента перечисления покажет только имя enum и имя члена enum:

>>> print(Weekday.THURSDAY)
Weekday.THURSDAY

Функция type() для элемента перечисления покажет, какому enum он принадлежит:

>>> type(Weekday.MONDAY)
< enum 'Weekday'>
>>> isinstance(Weekday.FRIDAY, Weekday)
True

Элементы Enum имеют атрибут name, который просто содержит их имя:

>>> print(Weekday.TUESDAY.name)
TUESDAY

Подобным образом есть атрибут value, который содержит значение элемента Enum:

>>> Weekday.WEDNESDAY.value
3

В отличие от многих языков программирования, которые обрабатывают перечисления чисто только как пары имя/значение, Python Enum может иметь добавленное поведение. Например, datetime.date содержит 2 метода для возврата недели: weekday() и isoweekday(). Они различаются тем, что один считает от 0 до 6, а другой от 1 до 7. Вместо того, чтобы отслеживать это самостоятельно, мы может добавить метод в перечисление Weekday, чтобы извлечь день из экземпляра date, и возвратить соответствующий элемент перечисления:

@classmethod
def from_date(cls, date): return cls(date.isoweekday())

Полное определение Weekday enum теперь выглядит так:

>>> 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 = {
... 'Покормить кошку': Weekday.MONDAY | Weekday.WEDNESDAY | Weekday.FRIDAY,
... 'Помыть посуду': Weekday.TUESDAY | Weekday.THURSDAY,
... 'Ответить на вопросы': 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) Ответить на вопросы

В тех случаях, когда реальные значения элементов Flag не имеют значения, можно сэкономить усилия, используя 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>

Если у вас есть элемент перечисления, в необходимо его имя или значение:

>>> member = Color.RED
>>> member.name
'RED'
>>> member.value
1

[Дублирование элементов и значений enum]

Недопустимо давать двум элементам перечисления одинаковые имена:

>>> class Shape(Enum):
... SQUARE = 2
... SQUARE = 3
... Traceback (most recent call last): ... TypeError: 'SQUARE' already defined as 2

Однако элементы перечисления могут иметь имена, связанные с другим именем. Если дать элементам A и B одинаковое значение, и A был определен первым, то B становится псевдонимом (alias) для элемента 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_() должен быть определен перед любыми элементами.

[Итерация]

Итерация по элементам enum не предоставляет псевдонимы:

>>> 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__ это упорядоченное read-only отображение имен на элементы перечисления. Он включает все имена, определенные в перечислении, в том числе и псевдонимы:

>>> 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

Предупреждение: можно перезагрузить модули – если перезагруженный модуль содержит перечисления, то они будут созданы заново, и новые элементы могут не быть идентичны/эквивалентны оригинальным элементам.

[Разрешенные элементы и атрибуты перечислений]

Большинство примеров выше используют целые числа для значений перечисления. Использование целых чисел это краткий и удобный способ (и он предоставляется по умолчанию в Functional 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'

Правила для дозволенного следующие: имена, которые начинаются и заканчиваются одним символом подчеркивания, зарезервированы перечислением и не могут использоваться; все другие атрибуты, определенные в перечислении, станут членами этого перечисления, за исключением специальных методов (__str__(), __add__(), и т. п.), дескрипторов (методы тоже являются дескрипторами), и имен переменных, перечисленных в _ignore_.

Замечание: если ваше перечисление определяет __new__() и/или __init__(), то любое значение (значения) указанные для элементов enum будут переданы в эти методы. В качестве примера см. класс Planet.

Обратите внимание, что метод __new__(), если определен, используется во время создания элементов Enum; затем он заменяется новым Enum-методом __new__(), который используется после создания класса для поиска существующих элементов. Для дополнительной информации см. "Когда использовать __new__(), а когда __init__()".

[Ограничение создания субклассов Enum]

Новый класс Enum должен иметь один базовый перечисления, до одного конкретного типа данных, и столько смешанных, основанных на объектах классов (object-based mixin classes) сколько необходимо. Порядок этих базовых классов:

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>

Используйте аргумент repr=False для dataclass(), чтобы использовать стандартную repr().

Изменено в версии 3.12: в области значений отображаются только поля dataclass, а не имя dataclass.

Замечание: добавление декоратора dataclass() для Enum и его субклассов не поддерживается. Это не вызовет никаких ошибок, но приведет к очень странным результатам runtime, например, если элементы равны друг другу:

>>> @dataclass     # не делайте так, это бессмысленно
... class Color(Enum):
... RED = 1 ... BLUE = 2
...
>>> Color.RED is Color.BLUE
False
>>> Color.RED == Color.BLUE # проблема: здесь не должно быть равенства
True

[Pickling]

Перечисления могут быть pickled и unpickled [4].

>>> from test.test_enum import Fruit
>>> from pickle import dumps, loads
>>> Fruit.TOMATO is loads(dumps(Fruit.TOMATO))
True

Применяются обычные ограничения для pickling: picklable-перечисления должны быть определены на верхнем уровне модуля, поскольку unpickling требует, чтобы они могли быть импортированы из этого модуля.

Замечание: с pickle-протоколом версии 4 есть возможность просто делать pickle перечислений, вложенных другие классы.

Можно изменить, как элементы enum будут pickled/unpickled, путем определения __reduce_ex__() в классе перечисления. Метод по умолчанию by-value (по значению), однако перечисления со сложными значениями могут захотеть вариант by-name (по имени):

>>> import enum
class MyEnum(enum.Enum):
... __reduce_ex__ = enum.pickle_by_enum_name

Замечание: вариант by-name для флагов не рекомендуется, поскольку не именованные псевдонимы (unnamed aliases) не пройдут операцию unpickle.

[Functional API]

Класс Enum является callable, предоставляя функциональное 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 является имя перечисления.

Второй аргумент это источник (source) имен элементов перечисления. Это может быть строка имен, где имена разделены пробелами, последовательность имен, последовательность 2-tuple с парами key/value, или mapping (отображение, т. е. словарь) имен на значения. Последние две опции позволяют назначать произвольные значения для перечислений; другие автоматически присваивают возрастающие целые числа, начиная с 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__)

Предупреждение: если модуль не предоставлен, то Enum не может определить, что это такое, новые элементы Enum будут unpicklable; чтобы держать ошибки ближе к источнику, 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: это то, что новый класс enum запишет в качестве своего имени.

names: элементы enum. Это может быть строка с разделителем пробелом или запятой (значения начнутся с 1, если не указано что-то другое):

'RED GREEN BLUE' | 'RED,GREEN,BLUE' | 'RED, GREEN, BLUE'

.. или итератор имен:

['RED', 'GREEN', 'BLUE']

.. или итератор пар (name, value):

[('CYAN', 4), ('MAGENTA', 5), ('YELLOW', 6)]

.. или отображение (mapping):

{'CHARTREUSE': 7, 'SEA_GREEN': 11, 'ROSEMARY': 42}

module: имя модуля, где можно найти новый класс enum.

qualname: где в модуле можно найти новый класс enum.

type: тип для смешивания с новым классом enum.

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 [2].

Добавлено в версии 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>

Замечание: именованные комбинации считаются псевдонимами. Псевдонимы не показываются в итерации, однако могут быть возвращены обращениями по значению (by-value lookup).

Изменено в версии 3.11.

Другое важное отличие IntFlag и Enum в том, что если ни один флаг элемента не установлен (значение 0), то его двоичная оценка равна False:

>>> Perm.R & Perm.X
< Perm: 0>
>>> bool(Perm.R & Perm.X)
False

Поскольку элементы IntFlag также являются субклассами от int, они могут комбинироваться с int (однако могут потерять членство в IntFlag):

>>> Perm.X | 4
< Perm.R|X: 5>>>> Perm.X + 89

Замечание: оператор отрицания ~ всегда возвратит элемент 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):   # или Enum вместо ReprEnum
    pass

Это демонстрирует, как как можно определить подобные производные перечисления; например, FloatEnum, который смешивается в float вместо int.

Некоторые правила:

1. Когда выполняется наследование от Enum, mix-in типы должны появляться перед самим классом Enum в последовательности базовых классов, как в показанном выше примере IntEnum.

2. Mix-in типы должны быть способны к созданию производных классов (subclassable). Например, bool и range не являются subclassable, и будет выброшено исключение ошибки (throw error) при создании Enum, если они будут использоваться в качестве mix-in типа.

3. Хотя Enum может иметь элементы любого типа, после смешивания в дополнительный тип все члены должны иметь значения этого типа, например int как в примере выше. Это ограничение не применяется mix-in типам, в которых только добавляются методы, и не указывается другой тип.

4. При смешивании другого типа данных атрибут value не совпадает с самим элементом перечисления, хотя он эквивалентен и при сравнении окажется равным.

5. Тип данных (data type) это mixin, который определяет __new__() или dataclass.

6. Форматирование в %-стиле: %s и %r вызывают соответственно методы __str__() и __repr__() класса Enum; другие коды формата (такие как %i или %h для IntEnum) обрабатывают элемент enum как его mixed-in тип.

7. Строковые литералы форматирования str.format() и format() будут использовать enum-метод __str__().

Замечание: поскольку IntEnum, IntFlag и StrEnum предназначены для замены существующих констант, их метод __str__() был сброшен в их тип данных метода __str__().

[Когда использовать __new__(), а когда __init__()]

Метод __new__() должен использоваться всякий раз, когда вы хотите переделать под себя реальное значение элемента Enum. Любые другие модификации могут произойти либо в __new__(), либо в __init__(), предпочтительнее в __init__().

Например, если вы хотите передать несколько элементов в конструктор, но только один из них должен быть значением:

>>> class Coordinate(bytes, Enum):
... """
... Координата с двоичными кодами, которые могут
... индексироваться кодом int.
... """
... 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__(), так как найдется lookup-only __new__; вместо этого используйте тип данных напрямую.

[Замечания о тонкостях]

Поддерживаемые имена __dunder__.

__members__ это упорядоченное read-only отображение элементов member_name:member. Это доступно только на классе.

__new__(), если указан, должен создать и возвратить элементы enum; также очень хорошей идеей будет установить _value_ элементов соответствующим образом. После создания всех элементов он больше не используется.

Поддерживаемые имена _sunder_. Назначение полей и методов:

_name_ – имя элемента.

_value_ – значение элемента; может быть установлено в __new__.

_missing_() – функция поиска, когда значение не найдено; этот метод может быть переопределен.

_ignore_ – список имен либо в виде списка, либо str, которые не будут преобразованы в элементы, и будут удалены их конечного класса.

_generate_next_value_() – используется для получения подходящего значения для элемента enum; этот метод может быть переопределен.

_add_alias_() – добавит новое имя как псевдоним к существующему элементу.

_add_value_alias_() – добавит новое значение как псевдоним существующего элемента. Для примера см. MultiValueEnum.

Замечание: для стандартных классов Enum следующее значение выбирается из самого большого видимого значения, инкрементированного на 1. Для классов 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_ необходим, поскольку порядок определения теряется, прежде чем его можно будет записать.

_Private__names. Приватные имена не преобразуются в элементы enum, однако остаются нормальными атрибутами.

Изменено в версии 3.11.

Тип элемента Enum. Элементы Enum являются экземплярами своего класса enum, и к ним нормально происходит обращение как к EnumClass.member. В определенных ситуациях, таких как написание пользовательского поведения enum, возможность доступа к одному элементу непосредственно их другого является полезной и поддерживается; однако во избежание конфликтов имен между именами элементов и атрибутами/методами из mixed-in классов, настоятельно рекомендуется использовать имена в верхнем регистре.

Изменено в версии 3.5.

Создание элементов, смешанных с другими типами данных. Когда выполняется наследование других типов данных, таких как 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

Boolean-значение классов Enum и его элементов. Классы Enum, которые смешиваются с не-Enum типами (такими как int, str, и т. д.) вычисляются в соответствии с правилами смешанных тиров (mixed-in type rules); иначе все элементы вычисляются как True. Чтобы создать ваше собственное вычисление boolean перечисления, зависящее от значения элемента, добавьте в свой класс следующее:

def __bool__(self):
    return bool(self.value)

Простые классы Enum всегда вычисляются как True.

Классы Enum с методами. Если вы дадите своим 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) # именованная комбинация флагов < Color.YELLOW: 3>
>>> Color(7) # не именованная комбинация флагов < Color.RED|GREEN|BLUE: 7>

Flag и minutia 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) # или 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

Существует новый механизм границ, который управляет, как обрабатываются биты вне диапазона (out-of-range) / недопустимые (invalid): STRICT, CONFORM, EJECT и KEEP:

• STRICT –> выбрасывается исключение, когда имеются недопустимые значения.

• CONFORM –> отбрасываются любые недопустимые биты.

• EJECT –> теряется статус Flag и становится обычным int с имеющимся значением.

• KEEP –> сохранение лишних бит:

   - сохраняется статус Flag и лишние биты.
   - лишние биты не показываются в итерации.
   - лишние биты не показываются в repr() и str().

Для Flag по умолчанию используется STRICT, для IntFlag используется умолчание EJECT, и умолчанием для _convert_ будет KEEP (см. ssl.Options [3] для примера, когда необходимо KEEP).

[Чем отличаются Enums и Flags]

Перечисления имеют пользовательский метакласс, который влияет на многие аспекты как производных классов Enum, так и их экземпляров (членов).

Классы Enum. Метакласс EnumType отвечает за предоставление __contains__(), __dir__(), __iter__() и других методов, которые позволяют делать такие вещи с классом Enum, которые терпят неудачу с типовым классом, таким как list(Color) или some_enum_var в Color. EnumType отвечает за то, чтобы различные другие методы в конечном классе Enum были корректными (такие как __new__(), __getnewargs__(), __str__() и __repr__()).

Классы Flag. Флаги имеют расширенный вид псевдонимизации: чтобы быть каноничным, значение флага должно быть степенью двойки, и не дублировать имя. Таким образом, в дополнение к определению псевдонима Enum, флаг без значения (также известный как 0) или с более чем одним значением степени двойки (например 3), считается псевдонимом.

Элементы Enum (также известные как экземпляры). Самое интересное в элементах enum: они являются синглтонами (singleton). EnumType создает их все, пока он создает сам класс enum, и затем помещает пользовательский __new__() по месту, чтобы гарантировать, что никакие новые экземпляры никогда не будут созданы, возвращая только существующие экземпляры-члены.

Из Википедии:

"В объектно-ориентированном программировании шаблон синглтона это шаблон разработки ПО, который ограничивает инстанциацию класса единственным экземпляром. Это один из известных шаблонов проектирования 'Банды четырех', который описывает, как решать повторяющиеся проблемы в объектно-ориентированном ПО. Подобный шаблон полезен, когда необходим ровно один объект для координации действий в системе.

Если более конкретно, то шаблон синглтона позволяет классам:

- гарантировать, что у них имеется только один экземпляр
- предоставить простой доступ к экземпляру
- управлять инстанциацией (например, скрывая конструктор класса).

Термин синглтона произошел из соответствующей математической концепции."

Элементы Flag. Элементы Flag могут быть возвращены итерацией, так же как класс Flag, и будут возвращены только канонические элементы. Например (обратите внимание, что не показаны BLACK, PURPLE и WHITE):

>>> list(Color)
[< Color.RED: 1>, < Color.GREEN: 2>, < Color.BLUE: 4>]

Инвертирование элемента флага возвратит соответствующее положительное значение вместо отрицательного значения, например:

>>> ~Color.RED
< Color.GREEN|BLUE: 6>

Элементы Flag имеют длину, соответствующую количеству значений степени двойки, которые они содержат. Например:

>>> len(Color.PURPLE)
2

[Enum Cookbook]

Хотя классы Enum, IntEnum, StrEnum, Flag и IntFlag покрывают большинство случаев использования, они не могут обслужить все возможные варианты применения. Ниже показаны рецепты для некоторых других типов перечислений, которые могут использоваться напрямую или в качестве примеров для создания ваших собственных классов.

Пропуск значений. Во многих случаях использования не имеет не важно, какое реальное значение у перечисления. Существует несколько способов определения такого простого типа перечисления:

• Использовать экземпляры auto для значения.
• Использовать экземпляры object в качестве значения.
• Использовать строку описания в качестве значения.
• Использовать кортеж (tuple) в качестве значения, и пользовательский __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): # единственное изменение предыдущего примера
... 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__(), поскольку будет найден только lookup-only __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):
... # универсальная константа гравитации (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.

[Ссылки]

1. Python enum HOWTO.
2. Python enum Support for enumerations.
3. ssl - TLS/SSL wrapper for socket objects.
4. Python FAQ.