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.

Примечание

Случай с членами Enum

Поскольку Enums используются для представления констант, а также для того, чтобы избежать проблем со столкновением имен между методами/атрибутами класса mixin и именами enum, мы настоятельно рекомендуем использовать UPPER_CASE имена для членов, и в наших примерах мы будем использовать именно этот стиль.

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

>>> Weekday(3)
<Weekday.WEDNESDAY: 3>

Как вы можете видеть, в repr() члена отображается имя перечисления, имя члена и значение. В str() члена отображается только имя перечисления и имя члена:

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

Тип* члена перечисления - это перечисление, к которому он принадлежит:

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

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

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

Точно так же у них есть атрибут для их value:

>>> Weekday.WEDNESDAY.value
3

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

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

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

>>> class Weekday(Enum):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 3
...     THURSDAY = 4
...     FRIDAY = 5
...     SATURDAY = 6
...     SUNDAY = 7
...     #
...     @classmethod
...     def from_date(cls, date):
...         return cls(date.isoweekday())

Теперь мы можем узнать, что сегодня за день! Наблюдайте:

>>> from datetime import date
>>> Weekday.from_date(date.today())     
<Weekday.TUESDAY: 2>

Конечно, если вы читаете это в какой-то другой день, вы увидите этот день.

Перечисление Weekday отлично подходит, если нашей переменной нужен только один день, но что, если нам нужно несколько? Может быть, мы пишем функцию для построения графика домашних дел в течение недели и не хотим использовать list. - мы можем использовать другой тип Enum:

>>> from enum import Flag
>>> class Weekday(Flag):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 4
...     THURSDAY = 8
...     FRIDAY = 16
...     SATURDAY = 32
...     SUNDAY = 64

Мы изменили две вещи: мы наследуем от Flag, и все значения равны 2.

Как и в исходном перечислении Weekday, мы можем иметь единственный выбор:

>>> first_week_day = Weekday.MONDAY
>>> first_week_day
<Weekday.MONDAY: 1>

Но Flag также позволяет нам объединить несколько членов в одну переменную:

>>> weekend = Weekday.SATURDAY | Weekday.SUNDAY
>>> weekend
<Weekday.SATURDAY|SUNDAY: 96>

Можно даже выполнить итерацию по переменной Flag:

>>> for day in weekend:
...     print(day)
Weekday.SATURDAY
Weekday.SUNDAY

Итак, давайте определимся с домашними делами:

>>> chores_for_ethan = {
...     'feed the cat': Weekday.MONDAY | Weekday.WEDNESDAY | Weekday.FRIDAY,
...     'do the dishes': Weekday.TUESDAY | Weekday.THURSDAY,
...     'answer SO questions': Weekday.SATURDAY,
...     }

И функция для отображения домашних дел на определенный день:

>>> def show_chores(chores, day):
...     for chore, days in chores.items():
...         if day in days:
...             print(chore)
...
>>> show_chores(chores_for_ethan, Weekday.SATURDAY)
answer SO questions

В случаях, когда фактические значения членов не имеют значения, вы можете сэкономить себе работу и использовать auto() для значений:

>>> from enum import auto
>>> class Weekday(Flag):
...     MONDAY = auto()
...     TUESDAY = auto()
...     WEDNESDAY = auto()
...     THURSDAY = auto()
...     FRIDAY = auto()
...     SATURDAY = auto()
...     SUNDAY = auto()
...     WEEKEND = SATURDAY | SUNDAY

Программный доступ к членам перечисления и их атрибутам

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

>>> Color(1)
<Color.RED: 1>
>>> Color(3)
<Color.BLUE: 3>

Если вы хотите получить доступ к членам перечисления по имени, используйте элемент access:

>>> Color['RED']
<Color.RED: 1>
>>> Color['GREEN']
<Color.GREEN: 2>

Если у вас есть член перечисления и вам нужен его name или value:

>>> 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 является псевдонимом для члена A. При поиске по значению A будет возвращен член A. При поиске по имени A будет возвращен член A. Поиск по имени B также вернет член A:

>>> class Shape(Enum):
...     SQUARE = 2
...     DIAMOND = 1
...     CIRCLE = 3
...     ALIAS_FOR_SQUARE = 2
...
>>> Shape.SQUARE
<Shape.SQUARE: 2>
>>> Shape.ALIAS_FOR_SQUARE
<Shape.SQUARE: 2>
>>> Shape(2)
<Shape.SQUARE: 2>

Примечание

Попытка создать член с тем же именем, что и уже определенный атрибут (другой член, метод и т. д.), или попытка создать атрибут с тем же именем, что и член, недопустима.

Обеспечение уникальных значений перечислений

По умолчанию перечисления позволяют использовать несколько имен в качестве псевдонимов для одного и того же значения. Если такое поведение нежелательно, можно использовать декоратор unique():

>>> from enum import Enum, unique
>>> @unique
... class Mistake(Enum):
...     ONE = 1
...     TWO = 2
...     THREE = 3
...     FOUR = 3
...
Traceback (most recent call last):
...
ValueError: duplicate values found in <enum 'Mistake'>: FOUR -> THREE

Использование автоматических значений

Если точное значение неважно, можно использовать auto:

>>> from enum import Enum, auto
>>> class Color(Enum):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> [member.value for member in Color]
[1, 2, 3]

Значения выбираются _generate_next_value_(), которые можно переопределить:

>>> class AutoName(Enum):
...     @staticmethod
...     def _generate_next_value_(name, start, count, last_values):
...         return name
...
>>> class Ordinal(AutoName):
...     NORTH = auto()
...     SOUTH = auto()
...     EAST = auto()
...     WEST = auto()
...
>>> [member.value for member in Ordinal]
['NORTH', 'SOUTH', 'EAST', 'WEST']

Примечание

Метод _generate_next_value_() должен быть определен до всех членов.

Итерация

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

>>> list(Shape)
[<Shape.SQUARE: 2>, <Shape.DIAMOND: 1>, <Shape.CIRCLE: 3>]
>>> list(Weekday)
[<Weekday.MONDAY: 1>, <Weekday.TUESDAY: 2>, <Weekday.WEDNESDAY: 4>, <Weekday.THURSDAY: 8>, <Weekday.FRIDAY: 16>, <Weekday.SATURDAY: 32>, <Weekday.SUNDAY: 64>]

Обратите внимание, что псевдонимы Shape.ALIAS_FOR_SQUARE и Weekday.WEEKEND не показаны.

Специальный атрибут __members__ представляет собой упорядоченное отображение имен на члены, доступное только для чтения. Он включает все имена, определенные в перечислении, в том числе псевдонимы:

>>> for name, member in Shape.__members__.items():
...     name, member
...
('SQUARE', <Shape.SQUARE: 2>)
('DIAMOND', <Shape.DIAMOND: 1>)
('CIRCLE', <Shape.CIRCLE: 3>)
('ALIAS_FOR_SQUARE', <Shape.SQUARE: 2>)

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

>>> [name for name, member in Shape.__members__.items() if member.name != name]
['ALIAS_FOR_SQUARE']

Примечание

Псевдонимы для флагов включают значения с несколькими установленными флагами, например 3, и без установленных флагов, т. е. 0.

Сравнения

Члены перечисления сравниваются по признаку идентичности:

>>> Color.RED is Color.RED
True
>>> Color.RED is Color.BLUE
False
>>> Color.RED is not Color.BLUE
True

Упорядоченные сравнения между значениями перечислений не поддерживаются. Члены перечисления не являются целыми числами (но см. IntEnum ниже):

>>> Color.RED < Color.BLUE
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'Color' and 'Color'

Сравнение равенств определяется следующим образом:

>>> Color.BLUE == Color.RED
False
>>> Color.BLUE != Color.RED
True
>>> Color.BLUE == Color.BLUE
True

Сравнение со значениями, не являющимися перечислениями, всегда будет сравнивать неравные (опять же, IntEnum был явно предназначен для другого поведения, см. ниже):

>>> Color.BLUE == 2
False

Предупреждение

Можно перезагружать модули - если перезагружаемый модуль содержит перечисления, они будут созданы заново, и новые члены могут быть не идентичны/равны исходным.

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

В большинстве приведенных выше примеров для значений перечисления используются целые числа. Использование целых чисел является коротким и удобным (и обеспечивается по умолчанию с помощью Functional API), но не является строго обязательным. В подавляющем большинстве случаев использования перечисления неважно, каково его фактическое значение. Но если значение важно, перечисления могут иметь произвольные значения.

Перечисления - это классы Python, и они могут иметь методы и специальные методы, как обычно. Если у нас есть такое перечисление:

>>> class Mood(Enum):
...     FUNKY = 1
...     HAPPY = 3
...
...     def describe(self):
...         # self is the member here
...         return self.name, self.value
...
...     def __str__(self):
...         return 'my custom str! {0}'.format(self.value)
...
...     @classmethod
...     def favorite_mood(cls):
...         # cls here is the enumeration
...         return cls.HAPPY
...

Затем:

>>> Mood.favorite_mood()
<Mood.HAPPY: 3>
>>> Mood.HAPPY.describe()
('HAPPY', 3)
>>> str(Mood.FUNKY)
'my custom str! 1'

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

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

Примечание

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

Ограниченный подкласс Enum

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

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 в __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() к Enum и его подклассам не поддерживается. Это не вызовет ошибок, но приведет к очень странным результатам во время выполнения, например, члены будут равны друг другу:

>>> @dataclass               # don't do this: it does not make any sense
... class Color(Enum):
...    RED = 1
...    BLUE = 2
...
>>> Color.RED is Color.BLUE
False
>>> Color.RED == Color.BLUE  # problem is here: they should not be equal
True

Маринование

Перечисления могут быть маринованными и немаринованными:

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

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

Примечание

С помощью протокола pickle версии 4 можно легко собирать перечисления, вложенные в другие классы.

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

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

Примечание

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

Функциональный API

Класс Enum является вызываемым и предоставляет следующий функциональный API:

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG')
>>> Animal
<enum 'Animal'>
>>> Animal.ANT
<Animal.ANT: 1>
>>> list(Animal)
[<Animal.ANT: 1>, <Animal.BEE: 2>, <Animal.CAT: 3>, <Animal.DOG: 4>]

Семантика этого API похожа на namedtuple. Первым аргументом вызова Enum является имя перечисления.

Второй аргумент - это источник имен членов перечисления. Это может быть строка имен, разделенная пробелами, последовательность имен, последовательность 2-кортежей с парами ключ/значение или отображение (например, словарь) имен на значения. Последние два варианта позволяют присваивать перечислениям произвольные значения; остальные автоматически присваивают возрастающие целые числа, начиная с 1 (используйте параметр start, чтобы указать другое начальное значение). Возвращается новый класс, производный от Enum. Другими словами, приведенное выше присвоение Animal эквивалентно:

>>> class Animal(Enum):
...     ANT = 1
...     BEE = 2
...     CAT = 3
...     DOG = 4
...

Причина использования по умолчанию 1 в качестве начального числа, а не 0 заключается в том, что 0 является False в булевом смысле, но по умолчанию все члены перечисления оцениваются в True.

Очистка перечислений, созданных с помощью функционального API, может оказаться непростой задачей, поскольку детали реализации стека кадров используются для того, чтобы попытаться определить, в каком модуле создается перечисление (например, это не сработает, если вы используете служебную функцию в отдельном модуле, а также может не сработать на IronPython или Jython). Решением является явное указание имени модуля следующим образом:

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__)

Предупреждение

Если module не предоставлен, и Enum не может определить, что это такое, новые члены Enum не будут распикированы; чтобы сохранить ошибки ближе к источнику, распикирование будет отключено.

Новый протокол pickle 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,
    )
  • значение: То, что новый класс перечисления будет записывать в качестве своего имени.

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

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

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

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

    или итератор пар (имя, значение):

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

    или отображение:

    {'CHARTREUSE': 7, 'SEA_GREEN': 11, 'ROSEMARY': 42}
    
  • module: имя модуля, в котором можно найти новый класс перечисления.

  • qualname: где в модуле можно найти новый класс перечисления.

  • type: тип для добавления в новый класс перечисления.

  • start: число, с которого следует начинать отсчет, если переданы только имена.

Изменено в версии 3.5: Был добавлен параметр start.

Производные перечисления

IntEnum

Первая предоставленная вариация Enum также является подклассом int. Члены IntEnum можно сравнивать с целыми числами; в свою очередь, целые перечисления разных типов также можно сравнивать друг с другом:

>>> from enum import IntEnum
>>> class Shape(IntEnum):
...     CIRCLE = 1
...     SQUARE = 2
...
>>> class Request(IntEnum):
...     POST = 1
...     GET = 2
...
>>> Shape == 1
False
>>> Shape.CIRCLE == 1
True
>>> Shape.CIRCLE == Request.POST
True

Однако их все равно нельзя сравнивать со стандартными перечислениями Enum:

>>> class Shape(IntEnum):
...     CIRCLE = 1
...     SQUARE = 2
...
>>> class Color(Enum):
...     RED = 1
...     GREEN = 2
...
>>> Shape.CIRCLE == Color.RED
False

Значения IntEnum ведут себя как целые числа в других отношениях, которые вы ожидаете:

>>> int(Shape.CIRCLE)
1
>>> ['a', 'b', 'c'][Shape.CIRCLE]
'b'
>>> [i for i in range(Shape.SQUARE)]
[0, 1]

StrEnum

Второй вариант Enum, который предоставляется, также является подклассом str. Члены StrEnum можно сравнивать со строками; кроме того, строковые перечисления разных типов можно сравнивать друг с другом.

Added in version 3.11.

IntFlag

Следующая вариация Enum, IntFlag, также основана на int. Разница в том, что члены IntFlag можно объединять с помощью побитовых операторов (&, |, ^, ~), и результатом будет член IntFlag, если это возможно. Как и IntEnum, члены IntFlag также являются целыми числами и могут использоваться везде, где используется int.

Примечание

Любая операция над членом IntFlag, кроме побитовых операций, приводит к потере членства IntFlag.

При побитовых операциях, приводящих к недопустимым значениям IntFlag, членство в IntFlag теряется. Подробности см. в разделе FlagBoundary.

Added in version 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>]

Added in version 3.11.

Флаг

Последним вариантом является Flag. Как и IntFlag, члены Flag можно объединять с помощью побитовых операторов (&, |, ^, ~). В отличие от IntFlag, их нельзя объединять и сравнивать с другими перечислениями Flag, а также int. Хотя можно указывать значения напрямую, рекомендуется использовать auto в качестве значения и позволить Flag выбрать подходящее значение.

Added in version 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>]

Added in version 3.11.

Примечание

Для большинства нового кода настоятельно рекомендуется использовать Enum и Flag, поскольку IntEnum и IntFlag нарушают некоторые семантические обещания перечислений (будучи сравнимыми с целыми числами и, следовательно, транзитивными по отношению к другим несвязанным перечислениям). IntEnum и IntFlag следует использовать только в тех случаях, когда Enum и Flag не подходят; например, когда целочисленные константы заменяются перечислениями, или для совместимости с другими системами.

Другие

Хотя IntEnum является частью модуля enum, его было бы очень просто реализовать независимо:

class IntEnum(int, ReprEnum):   # or Enum instead of ReprEnum
    pass

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

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

  1. При подклассификации Enum смешиваемые типы должны появляться перед самим классом Enum в последовательности баз, как в примере IntEnum выше.

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

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

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

  5. data type - это миксин, определяющий __new__(), или dataclass.

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

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

Примечание

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

Когда использовать __new__() против __init__()

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

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

>>> class Coordinate(bytes, Enum):
...     """
...     Coordinate with binary codes that can be indexed by the int code.
...     """
...     def __new__(cls, value, label, unit):
...         obj = bytes.__new__(cls, [value])
...         obj._value_ = value
...         obj.label = label
...         obj.unit = unit
...         return obj
...     PX = (0, 'P.X', 'km')
...     PY = (1, 'P.Y', 'km')
...     VX = (2, 'V.X', 'km/s')
...     VY = (3, 'V.Y', 'km/s')
...

>>> print(Coordinate['PY'])
Coordinate.PY

>>> print(Coordinate(3))
Coordinate.VY

Предупреждение

*Не вызывайте super().__new__(), так как будет найден только __new__; вместо этого используйте тип данных напрямую.

Тонкости

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

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

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

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

  • _name_ – имя участника

  • _value_ – значение члена; может быть задано в __new__

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

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

  • _generate_next_value_() – используется для получения соответствующего значения для члена перечисления; может быть переопределено

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

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

    Примечание

    Для стандартных классов Enum следующим выбирается наибольшее увиденное значение, увеличенное на единицу.

    Для классов Flag в качестве следующего значения будет выбрана наибольшая степень двойки.

    Изменено в версии 3.13: В предыдущих версиях вместо наибольшего значения использовалось последнее замеченное значение.

Added in version 3.6: _missing_, _order_, _generate_next_value_

Added in version 3.7: _ignore_

Added in version 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

Private names не преобразуются в члены перечисления, а остаются обычными атрибутами.

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

Enum тип члена

Члены перечисления являются экземплярами своего класса перечисления и обычно доступны как EnumClass.member. В некоторых ситуациях, например, при написании пользовательского поведения перечисления, возможность доступа к одному члену непосредственно из другого является полезной и поддерживается; однако, чтобы избежать столкновения имен членов и атрибутов/методов из смешанных классов, настоятельно рекомендуется использовать имена в верхнем регистре.

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

Создание членов, смешанных с другими типами данных

При подклассификации других типов данных, таких как int или str, с помощью Enum, все значения после = передаются в конструктор этого типа данных. Например:

>>> class MyEnum(IntEnum):      # help(int) -> int(x, base=10) -> integer
...     example = '11', 16      # so x='11' and base=16
...
>>> MyEnum.example.value        # and hex(11) is...
17

Булево значение классов и членов Enum

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

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

Обычные классы Enum всегда оцениваются как 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)  # named combination
<Color.YELLOW: 3>
>>> Color(7)      # not named combination
<Color.RED|GREEN|BLUE: 7>

Flag и IntFlag миниатюры

В качестве примера мы используем следующий фрагмент:

>>> class Color(IntFlag):
...     BLACK = 0
...     RED = 1
...     GREEN = 2
...     BLUE = 4
...     PURPLE = RED | BLUE
...     WHITE = RED | GREEN | BLUE
...

следующие утверждения верны:

  • Однобитные флаги являются каноническими

  • многобитные и нуль-битные флаги являются псевдонимами

  • во время итерации возвращаются только канонические флаги:

    >>> list(Color.WHITE)
    [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
    
  • Отрицание флага или набора флагов возвращает новый флаг/набор флагов с соответствующим положительным целым значением:

    >>> Color.BLUE
    <Color.BLUE: 4>
    
    >>> ~Color.BLUE
    <Color.RED|GREEN: 3>
    
  • имена псевдофлагов строятся из имен их членов:

    >>> (Color.RED | Color.GREEN).name
    'RED|GREEN'
    
  • Мультибитные флаги, они же псевдонимы, могут быть возвращены из операций:

    >>> Color.RED | Color.BLUE
    <Color.PURPLE: 5>
    
    >>> Color(7)  # or Color(-1)
    <Color.WHITE: 7>
    
    >>> Color(0)
    <Color.BLACK: 0>
    
  • проверка принадлежности/содержания: флаги с нулевым значением всегда считаются содержащимися:

    >>> Color.BLACK in Color.WHITE
    True
    

    иначе, только если все биты одного флага находятся в другом флаге, будет возвращено True:

    >>> Color.PURPLE in Color.WHITE
    True
    
    >>> Color.GREEN in Color.PURPLE
    False
    

Существует новый пограничный механизм, который управляет тем, как обрабатываются биты, выходящие за пределы диапазона / недействительные биты: STRICT, CONFORM, EJECT и KEEP:

  • STRICT –> вызывает исключение при представлении недопустимых значений

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

  • EJECT –> теряет статус флага и становится обычным int с заданным значением

  • KEEP –> сохранить дополнительные биты

    • сохраняет статус флага и дополнительные биты

    • Лишние биты не отображаются в итерации

    • лишние биты отображаются в repr() и str()

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

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

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

Классы Enum

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

Классы флагов

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

Члены Enum (они же экземпляры)

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

Члены флага

Члены флага можно итерировать, как и в классе Flag, при этом будут возвращены только канонические члены. Например:

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

(Обратите внимание, что BLACK, PURPLE и WHITE не отображаются).

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

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

Члены флага имеют длину, соответствующую количеству содержащихся в них двоек. Например:

>>> len(Color.PURPLE)
2

Поваренная книга Enum

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

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

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

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

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

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

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

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

Используя auto

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

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

Используя object

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

>>> class Color(Enum):
...     RED = object()
...     GREEN = object()
...     BLUE = object()
...
>>> Color.GREEN                         
<Color.GREEN: <object object at 0x...>>

Это также хороший пример того, почему вы можете захотеть написать свой собственный __repr__():

>>> class Color(Enum):
...     RED = object()
...     GREEN = object()
...     BLUE = object()
...     def __repr__(self):
...         return "<%s.%s>" % (self.__class__.__name__, self._name_)
...
>>> Color.GREEN
<Color.GREEN>

Использование описательной строки

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

>>> class Color(Enum):
...     RED = 'stop'
...     GREEN = 'go'
...     BLUE = 'too fast!'
...
>>> Color.GREEN
<Color.GREEN: 'go'>

Использование пользовательского __new__()

Использование автоматической нумерации __new__() будет выглядеть так:

>>> class AutoNumber(Enum):
...     def __new__(cls):
...         value = len(cls.__members__) + 1
...         obj = object.__new__(cls)
...         obj._value_ = value
...         return obj
...
>>> class Color(AutoNumber):
...     RED = ()
...     GREEN = ()
...     BLUE = ()
...
>>> Color.GREEN
<Color.GREEN: 2>

Чтобы сделать более универсальный AutoNumber, добавьте *args к сигнатуре:

>>> class AutoNumber(Enum):
...     def __new__(cls, *args):      # this is the only change from above
...         value = len(cls.__members__) + 1
...         obj = object.__new__(cls)
...         obj._value_ = value
...         return obj
...

Затем, когда вы наследуете от AutoNumber, вы можете написать свой собственный __init__ для обработки любых дополнительных аргументов:

>>> class Swatch(AutoNumber):
...     def __init__(self, pantone='unknown'):
...         self.pantone = pantone
...     AUBURN = '3497'
...     SEA_GREEN = '1246'
...     BLEACHED_CORAL = () # New color, no Pantone code yet!
...
>>> Swatch.SEA_GREEN
<Swatch.SEA_GREEN: 2>
>>> Swatch.SEA_GREEN.pantone
'1246'
>>> Swatch.BLEACHED_CORAL.pantone
'unknown'

Примечание

Метод __new__(), если он определен, используется при создании членов Enum; затем он заменяется методом __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'>

Планета

Если определено значение __new__() или __init__(), то значение члена перечисления будет передано в эти методы:

>>> class Planet(Enum):
...     MERCURY = (3.303e+23, 2.4397e6)
...     VENUS   = (4.869e+24, 6.0518e6)
...     EARTH   = (5.976e+24, 6.37814e6)
...     MARS    = (6.421e+23, 3.3972e6)
...     JUPITER = (1.9e+27,   7.1492e7)
...     SATURN  = (5.688e+26, 6.0268e7)
...     URANUS  = (8.686e+25, 2.5559e7)
...     NEPTUNE = (1.024e+26, 2.4746e7)
...     def __init__(self, mass, radius):
...         self.mass = mass       # in kilograms
...         self.radius = radius   # in meters
...     @property
...     def surface_gravity(self):
...         # universal gravitational constant  (m3 kg-1 s-2)
...         G = 6.67300E-11
...         return G * self.mass / (self.radius * self.radius)
...
>>> Planet.EARTH.value
(5.976e+24, 6378140.0)
>>> Planet.EARTH.surface_gravity
9.802652743337129

TimePeriod

Пример использования атрибута _ignore_:

>>> from datetime import timedelta
>>> class Period(timedelta, Enum):
...     "different lengths of time"
...     _ignore_ = 'Period i'
...     Period = vars()
...     for i in range(367):
...         Period['day_%d' % i] = i
...
>>> list(Period)[:2]
[<Period.day_0: datetime.timedelta(0)>, <Period.day_1: datetime.timedelta(days=1)>]
>>> list(Period)[-2:]
[<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>]

Подклассификация EnumType

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