Spec-Zone.ru › Python 3.13

Руководство по Enum

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

Поскольку перечисления используются для представления констант и помогают избегать конфликтов имён между методами/атрибутами классов-миксинов и именами элементов перечисления, мы настоятельно рекомендуем использовать для элементов имена в ВЕРХНЕМ РЕГИСТРЕ и будем придерживаться этого стиля в наших примерах.

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

>>> 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, а значения являются степенями двойки.

Как и в исходном перечислении 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):
...     @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

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

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

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

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

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

class EnumName([mix-in, ...,] [data-type,] base-enum):
    pass

Кроме того, наследование от перечисления допускается, только если в нём не определено ни одного элемента. Поэтому следующий пример запрещён:

>>> class MoreColor(Color):
...     PINK = 17
...
Traceback (most recent call last):
...
TypeError: <enum 'MoreColor'> cannot extend <enum 'Color'>

А следующий разрешён:

>>> class Foo(Enum):
...     def some_behavior(self):
...         pass
...
>>> class Bar(Foo):
...     HAPPY = 1
...     SAD = 2
...

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

Поддержка dataclass

При наследовании от dataclass метод __repr__() не включает имя родительского класса. Например:

>>> from dataclasses import dataclass, field
>>> @dataclass
... class CreatureDataMixin:
...     size: str
...     legs: int
...     tail: bool = field(repr=False, default=True)
...
>>> class Creature(CreatureDataMixin, Enum):
...     BEETLE = 'small', 6
...     DOG = 'medium', 4
...
>>> Creature.DOG
<Creature.DOG: size='medium', legs=4>

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

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

Примечание

Добавлять декоратор 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

Сериализация с помощью pickle

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

>>> 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 не может определить его значение, новые элементы Enum нельзя будет десериализовать; чтобы ошибки возникали ближе к источнику проблемы, сериализация с помощью pickle будет отключена.

Новый протокол 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.

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

IntEnum

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

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

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

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

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

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

StrEnum

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

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

IntFlag

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

Примечание

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

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

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

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

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

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

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

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

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

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

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

По элементам Flag также можно выполнять итерацию:

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

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

Примечание

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

Другие варианты

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

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

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

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

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

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

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

Добавлено в версии 3.13: _add_alias_, _add_value_alias_

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

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

Примечание

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

_Private__names

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

Изменено в версии 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 с методами

Если добавить в подкласс перечисления дополнительные методы, как в приведённом ниже классе 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'
    
    >>> class Perm(IntFlag):
    ...     R = 4
    ...     W = 2
    ...     X = 1
    ...
    >>> (Perm.R & Perm.W).name is None  # effectively Perm(0)
    True
    
  • многобитовые флаги, то есть псевдонимы, могут возвращаться в результате операций:

    >>> Color.RED | Color.BLUE
    <Color.PURPLE: 5>
    
    >>> Color(7)  # 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 –> снимает статус Flag и преобразует значение в обычное целое число
  • KEEP –> сохраняет дополнительные биты

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

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

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

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

Классы Enum

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

Классы Flag

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

Элементы Enum (то есть экземпляры)

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

Элементы Flag

По элементам флагов можно выполнять итерацию так же, как по классу 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'>

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       # 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 можно наследовать, чтобы получить другой опыт работы с Enum.

© 2001–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/howto/enum.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API