Документация Python неофициальный перевод
Содержание страницы

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__().

Ограниченное создание подклассов EnumRestricted 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.)

Поддержка dataclassDataclass 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

Примечание

Использование по имени для флагов не рекомендуется, так как безымянные псевдонимы не будут распаковываться.

Функциональное APIFunctional 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

Следующая разновидность EnumIntFlag – также основана на 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.

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

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

  2. Примешиваемые типы должны быть наследуемыми. Например, bool и range не являются наследуемыми и приведут к ошибке во время создания Enum, если используются в качестве примешиваемого типа.

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

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

  5. data type – это примесь (mixin), которая определяет __new__(), или dataclass

  6. Форматирование в стиле %: %s и %r вызывают __str__() и __repr__() класса Enum соответственно; другие коды (например, %i или %h для IntEnum) обрабатывают член перечисления как его примешанный тип.

  7. Форматированные строковые литералы, str.format() и format() будут использовать метод __str__() перечисления.

Примечание

Поскольку IntEnum, IntFlag и StrEnum предназначены для замены существующих констант «на лету», их метод __str__() был переопределён на метод __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.

Тип члена EnumEnum 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

Логическое значение классов и членов EnumBoolean value of Enum classes and members

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

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

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

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

Объединение элементов FlagCombining 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, по умолчанию для IntFlagEJECT, а по умолчанию для _convert_KEEP (см. ssl.Options для примера, когда требуется KEEP).

Чем отличаются перечисления и флаги?How are Enums and Flags different?

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

Классы EnumEnum Classes

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

Классы FlagFlag 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

Сборник рецептов EnumEnum Cookbook

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

Пропуск значенийOmitting values

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

  • использовать экземпляры auto в качестве значения

  • использовать экземпляры object в качестве значения

  • использовать описательную строку в качестве значения

  • использовать кортеж в качестве значения и собственный __new__() для замены кортежа на значение int

Использование любого из этих методов показывает пользователю, что эти значения не важны, а также позволяет добавлять, удалять или переупорядочивать члены без необходимости перенумерации остальных членов.

Использование autoUsing auto

Использование auto будет выглядеть так:

>>> class Color(Enum):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.GREEN
<Color.GREEN: 3>

Использование objectUsing 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)>]

Создание подкласса EnumTypeSubclassing EnumType

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