Перечисление ИНСТРУКЦИЙ

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

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

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

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

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

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

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

Дублирование элементов и значений перечисления

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

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

Однако с элементом перечисления могут быть связаны и другие имена. Учитывая, что две записи A и B имеют одинаковое значение (и A определены первыми), B является псевдонимом для элемента A. При поиске значения A будет возвращен элемент A. При поиске значения A будет возвращен элемент A. Поиск по имени B также вернет элемент A:

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

Примечание

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

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

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

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

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

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

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

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

>>> class AutoName(Enum):
...     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'

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

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

Примечание

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

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

Новый класс 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).

Маринование

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

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

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

Примечание

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

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

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

Примечание

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

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

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

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

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

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

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

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

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

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

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

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

Новый протокол pickle 4 также, в некоторых случаях, основан на том, что __qualname__ устанавливается в положение, в котором pickle сможет найти класс. Например, если класс был доступен в class, некоторые данные будут доступны в глобальной области видимости:

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', qualname='SomeData.Animal')

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

Enum(
    value='NewEnumName',
    names=<...>,
    *,
    module='...',
    qualname='...',
    type=<mixed-in class>,
    start=1,
    )
  • значение: что новый класс enum будет записывать в качестве своего имени.

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

  • начало: число, с которого начинается отсчет, если вводятся только имена.

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

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

Интенсивность

Первая представленная вариация 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]

Напряжение

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

Добавлено в версии 3.11.

IntФлаг

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

Примечание

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

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

Добавлено в версии 3.6.

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

Примерный IntFlag класс:

>>> from enum import IntFlag
>>> class Perm(IntFlag):
...     R = 4
...     W = 2
...     X = 1
...
>>> Perm.R | Perm.W
<Perm.R|W: 6>
>>> Perm.R + Perm.W
6
>>> RW = Perm.R | Perm.W
>>> Perm.R in RW
True

Также можно дать название комбинациям:

>>> class Perm(IntFlag):
...     R = 4
...     W = 2
...     X = 1
...     RWX = 7
>>> Perm.RWX
<Perm.RWX: 7>
>>> ~Perm.RWX
<Perm: 0>
>>> Perm(7)
<Perm.RWX: 7>

Примечание

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

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

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

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

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

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

>>> Perm.X + 8
9

Примечание

Оператор отрицания ~ всегда возвращает элемент IntFlag с положительным значением:

>>> (~Perm.X).value == (Perm.R|Perm.W).value == 6
True

IntFlag элементы также могут быть перебраны:

>>> list(RW)
[<Perm.R: 4>, <Perm.W: 2>]

Добавлено в версии 3.11.

Флаг

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

Добавлено в версии 3.6.

Как и в случае с IntFlag, если комбинация элементов Flag не приводит к установке флагов, логическая оценка равна False:

>>> from enum import Flag, auto
>>> class Color(Flag):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.RED & Color.GREEN
<Color: 0>
>>> bool(Color.RED & Color.GREEN)
False

Отдельные флаги должны иметь значения, равные степени двойки (1, 2, 4, 8, …), в то время как комбинации флагов не будут:

>>> class Color(Flag):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...     WHITE = RED | BLUE | GREEN
...
>>> Color.WHITE
<Color.WHITE: 7>

Присвоение имени условию «флаги не установлены» не изменяет его логического значения:

>>> class Color(Flag):
...     BLACK = 0
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.BLACK
<Color.BLACK: 0>
>>> bool(Color.BLACK)
False

Flag элементы также могут быть перебраны:

>>> purple = Color.RED | Color.BLUE
>>> list(purple)
[<Color.RED: 1>, <Color.BLUE: 2>]

Добавлено в версии 3.11.

Примечание

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

Другие

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

class IntEnum(int, Enum):
    pass

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

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

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

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

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

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

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

  6. форматирование в стиле %-%: %s и %r вызывают Enum классы __str__() и __repr__() соответственно; другие коды (например, %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, которые не будут преобразованы в члены и будут удалены из конечного класса

  • _order_ – используется в коде Python 2/3 для обеспечения согласованности порядка элементов (атрибут class, удален при создании класса)

  • _generate_next_value_ - используется Functional API и auto для получения соответствующего значения для элемента перечисления; может быть переопределен

Примечание

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

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

Добавлено в версии 3.6: _missing_, _order_, _generate_next_value_

Добавлено в версии 3.7: _ignore_

Чтобы синхронизировать код на Python 2 / Python 3, можно указать атрибут _order_. Он будет проверен на соответствие фактическому порядку перечисления и выдаст ошибку, если они не совпадают:

>>> class Color(Enum):
...     _order_ = 'RED GREEN BLUE'
...     RED = 1
...     BLUE = 3
...     GREEN = 2
...
Traceback (most recent call last):
...
TypeError: member order does not match _order_:
  ['RED', 'BLUE', 'GREEN']
  ['RED', 'GREEN', 'BLUE']

Примечание

В коде на Python 2 атрибут _order_ необходим, поскольку порядок определения теряется до того, как он может быть записан.

_Личные__имена

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

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

Enum тип элемента

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

Изменено в версии 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 классов и членов

Классы перечислений, которые смешаны с типами, отличными от:class: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 –> вызывает исключение при представлении недопустимых значений

  • СОГЛАСОВАТЬ –> отбрасывает все недопустимые биты

  • УДАЛИТЬ –> потерять статус флага и стать обычным int с заданным значением

  • СОХРАНИТЬ -> сохранить лишние фрагменты

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

    • дополнительные биты не отображаются при повторении

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

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

Чем отличаются перечисления от флагов?

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

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

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

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

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

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

>>> 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; затем он заменяется методом Enum __new__(), который используется после создания класса для поиска существующих элементов.

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

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

obj = int.__new__(cls, value)

Упорядоченное число

Упорядоченное перечисление, которое не основано на 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

Дублирующий бесплатный номер

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

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

Планета

Если определено значение __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

Временной промежуток

Пример, показывающий использование атрибута _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 можно разделить на подклассы, чтобы обеспечить другой интерфейс перечисления.

Вернуться на верх