Spec-Zone.ru › Python 3.13

Перечисление HOWTO

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

Они наиболее полезны, когда переменная может принимать одно из ограниченного числа значений. Например, дни недели:

>>> from enum import Enum
>>> class Weekday(Enum):
...     MONDAY = 1
...     TUESDAY = 2
...     WEDNESDAY = 3
...     THURSDAY = 4
...     FRIDAY = 5
...     SATURDAY = 6
...     SUNDAY = 7

Или, возможно, основные цвета RGB:

>>> from enum import Enum
>>> class Color(Enum):
...     RED = 1
...     GREEN = 2
...     BLUE = 3

Как видите, создание перечисления (Enum) так же просто, как создание класса, который наследуется от самого перечисления (Enum).

Примечание

Регистр членов перечисления

Поскольку перечисления используются для представления констант и для предотвращения конфликтов имен между методами/атрибутами класса-миксина и именами перечисления, мы настоятельно рекомендуем использовать имена членов с ПРОПИСНЫМИ буквами и будем использовать этот стиль в наших примерах.

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

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

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

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

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

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

У членов перечисления есть атрибут, содержащий только их name:

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

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

>>> Weekday.WEDNESDAY.value
3

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

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

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

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

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

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

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

Это перечисление (Weekday) отлично подходит, если нашей переменной нужен только один день, но что, если нам нужно несколько? Возможно, мы пишем функцию для планирования домашних дел на неделю и не хотим использовать список — мы могли бы использовать другой тип перечисления (Enum) :

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

Мы изменили два пункта: мы унаследовали от Flag, и значения являются степенями двойки.

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

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

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

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

Вы даже можете перебрать переменную Flag:

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

Хорошо, давайте настроим некоторые домашние дела:

>>> chores_for_ethan = {
...     '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'

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

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

Примечание

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

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

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

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

Сериализация

Перечисления можно сериализовать и десериализовать:

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

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

Примечание

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

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

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

Полная сигнатура:

Enum(
    value='NewEnumName',
    names=<...>,
    *,
    module='...',
    qualname='...',
    type=<mixed-in class>,
    start=1,
    )
  • значение: то, что новый класс перечисления запишет как своё имя.
  • имена: члены перечисления. Это может быть строка имён, разделённых пробелами или запятыми (значения будут начинаться с 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}
    
  • модуль: имя модуля, где можно найти новый класс перечисления.
  • qualname: где в модуле можно найти новый класс перечисления.
  • тип: тип, который нужно добавить в новый класс перечисления.
  • начать: число, с которого нужно начать отсчёт, если переданы только имена.

Изменено в версии 3.5: Добавлен параметр начать.

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

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 не являются наследуемыми и приведут к ошибке при создании перечисления, если используются в качестве типа объединения.
  3. Хотя Enum может содержать члены любого типа, после добавления дополнительного типа все члены должны иметь значения этого типа, например, int выше. Это ограничение не распространяется на объединения, которые добавляют только методы и не указывают другой тип.
  4. Когда добавляется другой тип данных, атрибут value не такой же, как сам член перечисления, хотя они эквивалентны и будут сравниваться как равные.
  5. data type — это объединение, которое определяет __new__() или dataclass.
  6. Форматирование в стиле %: %s и %r вызывают методы Enum __str__() и __repr__() соответственно; другие коды (такие как %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_ необходим, так как порядок определения теряется, прежде чем он может быть записан.

_Приватные__имена

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

Изменено в версии 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 –> теряет статус флага и становится обычным целым числом с заданным значением
  • KEEP –> сохраняет дополнительные биты

    • сохраняет статус флага и дополнительные биты
    • дополнительные биты не отображаются при итерации
    • дополнительные биты отображаются в 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) рассматривается как алиас.

Члены перечислений (т.е. экземпляры)

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

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

Не вызывайте 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

Период времени

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

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

Spec-Zone.ru

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