Enum HOWTO¶
Переменная Enum
- это набор символических имен, связанных с уникальными значениями. Они похожи на глобальные переменные, но предлагают более полезный repr()
, группировку, безопасность типов и некоторые другие возможности.
Они наиболее полезны, когда у вас есть переменная, которая может принимать одно из ограниченного набора значений. Например, дни недели:
>>> from enum import Enum
>>> class Weekday(Enum):
... MONDAY = 1
... TUESDAY = 2
... WEDNESDAY = 3
... THURSDAY = 4
... FRIDAY = 5
... SATURDAY = 6
... SUNDAY = 7
Или, возможно, основные цвета RGB:
>>> from enum import Enum
>>> class Color(Enum):
... RED = 1
... GREEN = 2
... BLUE = 3
Как видите, создать Enum
так же просто, как написать класс, который наследуется от Enum
.
Примечание
Случай с членами Enum
Поскольку Enums используются для представления констант, а также для того, чтобы избежать проблем со столкновением имен между методами/атрибутами класса mixin и именами enum, мы настоятельно рекомендуем использовать UPPER_CASE имена для членов, и в наших примерах мы будем использовать именно этот стиль.
В зависимости от характера перечисления значение члена может быть важным или не важным, но в любом случае это значение может быть использовано для получения соответствующего члена:
>>> Weekday(3)
<Weekday.WEDNESDAY: 3>
Как вы можете видеть, в repr()
члена отображается имя перечисления, имя члена и значение. В str()
члена отображается только имя перечисления и имя члена:
>>> print(Weekday.THURSDAY)
Weekday.THURSDAY
Тип* члена перечисления - это перечисление, к которому он принадлежит:
>>> type(Weekday.MONDAY)
<enum 'Weekday'>
>>> isinstance(Weekday.FRIDAY, Weekday)
True
Члены Enum имеют атрибут, который содержит только их name
:
>>> print(Weekday.TUESDAY.name)
TUESDAY
Точно так же у них есть атрибут для их value
:
>>> Weekday.WEDNESDAY.value
3
В отличие от многих языков, в которых перечисления рассматриваются исключительно как пары имя/значение, в Python Enums можно добавлять поведение. Например, у datetime.date
есть два метода для возврата дня недели: weekday()
и isoweekday()
. Разница в том, что один из них считает от 0 до 6, а другой - от 1 до 7. Чтобы не отслеживать это самостоятельно, мы можем добавить метод в перечисление Weekday
, который будет извлекать день из экземпляра date
и возвращать соответствующий член перечисления:
@classmethod
def from_date(cls, date):
return cls(date.isoweekday())
Полное перечисление Weekday
теперь выглядит так:
>>> class Weekday(Enum):
... MONDAY = 1
... TUESDAY = 2
... WEDNESDAY = 3
... THURSDAY = 4
... FRIDAY = 5
... SATURDAY = 6
... SUNDAY = 7
... #
... @classmethod
... def from_date(cls, date):
... return cls(date.isoweekday())
Теперь мы можем узнать, что сегодня за день! Наблюдайте:
>>> from datetime import date
>>> Weekday.from_date(date.today())
<Weekday.TUESDAY: 2>
Конечно, если вы читаете это в какой-то другой день, вы увидите этот день.
Перечисление Weekday
отлично подходит, если нашей переменной нужен только один день, но что, если нам нужно несколько? Может быть, мы пишем функцию для построения графика домашних дел в течение недели и не хотим использовать list
. - мы можем использовать другой тип Enum
:
>>> from enum import Flag
>>> class Weekday(Flag):
... MONDAY = 1
... TUESDAY = 2
... WEDNESDAY = 4
... THURSDAY = 8
... FRIDAY = 16
... SATURDAY = 32
... SUNDAY = 64
Мы изменили две вещи: мы наследуем от Flag
, и все значения равны 2.
Как и в исходном перечислении Weekday
, мы можем иметь единственный выбор:
>>> first_week_day = Weekday.MONDAY
>>> first_week_day
<Weekday.MONDAY: 1>
Но Flag
также позволяет нам объединить несколько членов в одну переменную:
>>> weekend = Weekday.SATURDAY | Weekday.SUNDAY
>>> weekend
<Weekday.SATURDAY|SUNDAY: 96>
Можно даже выполнить итерацию по переменной Flag
:
>>> for day in weekend:
... print(day)
Weekday.SATURDAY
Weekday.SUNDAY
Итак, давайте определимся с домашними делами:
>>> chores_for_ethan = {
... 'feed the cat': Weekday.MONDAY | Weekday.WEDNESDAY | Weekday.FRIDAY,
... 'do the dishes': Weekday.TUESDAY | Weekday.THURSDAY,
... 'answer SO questions': Weekday.SATURDAY,
... }
И функция для отображения домашних дел на определенный день:
>>> def show_chores(chores, day):
... for chore, days in chores.items():
... if day in days:
... print(chore)
...
>>> show_chores(chores_for_ethan, Weekday.SATURDAY)
answer SO questions
В случаях, когда фактические значения членов не имеют значения, вы можете сэкономить себе работу и использовать auto()
для значений:
>>> from enum import auto
>>> class Weekday(Flag):
... MONDAY = auto()
... TUESDAY = auto()
... WEDNESDAY = auto()
... THURSDAY = auto()
... FRIDAY = auto()
... SATURDAY = auto()
... SUNDAY = auto()
... WEEKEND = SATURDAY | SUNDAY
Программный доступ к членам перечисления и их атрибутам¶
Иногда полезно получить программный доступ к членам перечислений (например, в ситуациях, когда Color.RED
не подходит, потому что точный цвет не известен на момент написания программы). Enum
позволяет получить такой доступ:
>>> Color(1)
<Color.RED: 1>
>>> Color(3)
<Color.BLUE: 3>
Если вы хотите получить доступ к членам перечисления по имени, используйте элемент access:
>>> Color['RED']
<Color.RED: 1>
>>> Color['GREEN']
<Color.GREEN: 2>
Если у вас есть член перечисления и вам нужен его name
или value
:
>>> member = Color.RED
>>> member.name
'RED'
>>> member.value
1
Дублирование членов и значений перечисления¶
Наличие двух членов enum с одинаковыми именами недопустимо:
>>> class Shape(Enum):
... SQUARE = 2
... SQUARE = 3
...
Traceback (most recent call last):
...
TypeError: 'SQUARE' already defined as 2
Однако член перечисления может иметь другие имена, связанные с ним. Если две записи A
и B
имеют одинаковое значение (и A
определена первой), то B
является псевдонимом для члена A
. При поиске по значению A
будет возвращен член A
. При поиске по имени A
будет возвращен член A
. Поиск по имени B
также вернет член A
:
>>> class Shape(Enum):
... SQUARE = 2
... DIAMOND = 1
... CIRCLE = 3
... ALIAS_FOR_SQUARE = 2
...
>>> Shape.SQUARE
<Shape.SQUARE: 2>
>>> Shape.ALIAS_FOR_SQUARE
<Shape.SQUARE: 2>
>>> Shape(2)
<Shape.SQUARE: 2>
Примечание
Попытка создать член с тем же именем, что и уже определенный атрибут (другой член, метод и т. д.), или попытка создать атрибут с тем же именем, что и член, недопустима.
Обеспечение уникальных значений перечислений¶
По умолчанию перечисления позволяют использовать несколько имен в качестве псевдонимов для одного и того же значения. Если такое поведение нежелательно, можно использовать декоратор unique()
:
>>> from enum import Enum, unique
>>> @unique
... class Mistake(Enum):
... ONE = 1
... TWO = 2
... THREE = 3
... FOUR = 3
...
Traceback (most recent call last):
...
ValueError: duplicate values found in <enum 'Mistake'>: FOUR -> THREE
Использование автоматических значений¶
Если точное значение неважно, можно использовать auto
:
>>> from enum import Enum, auto
>>> class Color(Enum):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> [member.value for member in Color]
[1, 2, 3]
Значения выбираются _generate_next_value_()
, которые можно переопределить:
>>> class AutoName(Enum):
... @staticmethod
... def _generate_next_value_(name, start, count, last_values):
... return name
...
>>> class Ordinal(AutoName):
... NORTH = auto()
... SOUTH = auto()
... EAST = auto()
... WEST = auto()
...
>>> [member.value for member in Ordinal]
['NORTH', 'SOUTH', 'EAST', 'WEST']
Примечание
Метод _generate_next_value_()
должен быть определен до всех членов.
Итерация¶
Итерация над членами перечисления не предоставляет псевдонимов:
>>> list(Shape)
[<Shape.SQUARE: 2>, <Shape.DIAMOND: 1>, <Shape.CIRCLE: 3>]
>>> list(Weekday)
[<Weekday.MONDAY: 1>, <Weekday.TUESDAY: 2>, <Weekday.WEDNESDAY: 4>, <Weekday.THURSDAY: 8>, <Weekday.FRIDAY: 16>, <Weekday.SATURDAY: 32>, <Weekday.SUNDAY: 64>]
Обратите внимание, что псевдонимы Shape.ALIAS_FOR_SQUARE
и Weekday.WEEKEND
не показаны.
Специальный атрибут __members__
представляет собой упорядоченное отображение имен на члены, доступное только для чтения. Он включает все имена, определенные в перечислении, в том числе псевдонимы:
>>> for name, member in Shape.__members__.items():
... name, member
...
('SQUARE', <Shape.SQUARE: 2>)
('DIAMOND', <Shape.DIAMOND: 1>)
('CIRCLE', <Shape.CIRCLE: 3>)
('ALIAS_FOR_SQUARE', <Shape.SQUARE: 2>)
Атрибут __members__
может быть использован для детального программного доступа к членам перечисления. Например, поиск всех псевдонимов:
>>> [name for name, member in Shape.__members__.items() if member.name != name]
['ALIAS_FOR_SQUARE']
Примечание
Псевдонимы для флагов включают значения с несколькими установленными флагами, например 3
, и без установленных флагов, т. е. 0
.
Сравнения¶
Члены перечисления сравниваются по признаку идентичности:
>>> Color.RED is Color.RED
True
>>> Color.RED is Color.BLUE
False
>>> Color.RED is not Color.BLUE
True
Упорядоченные сравнения между значениями перечислений не поддерживаются. Члены перечисления не являются целыми числами (но см. IntEnum ниже):
>>> Color.RED < Color.BLUE
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'Color' and 'Color'
Сравнение равенств определяется следующим образом:
>>> Color.BLUE == Color.RED
False
>>> Color.BLUE != Color.RED
True
>>> Color.BLUE == Color.BLUE
True
Сравнение со значениями, не являющимися перечислениями, всегда будет сравнивать неравные (опять же, IntEnum
был явно предназначен для другого поведения, см. ниже):
>>> Color.BLUE == 2
False
Предупреждение
Можно перезагружать модули - если перезагружаемый модуль содержит перечисления, они будут созданы заново, и новые члены могут быть не идентичны/равны исходным.
Разрешенные члены и атрибуты перечислений¶
В большинстве приведенных выше примеров для значений перечисления используются целые числа. Использование целых чисел является коротким и удобным (и обеспечивается по умолчанию с помощью Functional API), но не является строго обязательным. В подавляющем большинстве случаев использования перечисления неважно, каково его фактическое значение. Но если значение важно, перечисления могут иметь произвольные значения.
Перечисления - это классы Python, и они могут иметь методы и специальные методы, как обычно. Если у нас есть такое перечисление:
>>> class Mood(Enum):
... FUNKY = 1
... HAPPY = 3
...
... def describe(self):
... # self is the member here
... return self.name, self.value
...
... def __str__(self):
... return 'my custom str! {0}'.format(self.value)
...
... @classmethod
... def favorite_mood(cls):
... # cls here is the enumeration
... return cls.HAPPY
...
Затем:
>>> Mood.favorite_mood()
<Mood.HAPPY: 3>
>>> Mood.HAPPY.describe()
('HAPPY', 3)
>>> str(Mood.FUNKY)
'my custom str! 1'
Правила для разрешенных имен следующие: имена, которые начинаются и заканчиваются одним подчеркиванием, зарезервированы в перечислении и не могут быть использованы; все остальные атрибуты, определенные в перечислении, становятся членами этого перечисления, за исключением специальных методов (__str__()
, __add__()
и т. д.), дескрипторов (методы также являются дескрипторами) и имен переменных, перечисленных в _ignore_
.
Примечание: если ваше перечисление определяет __new__()
и/или __init__()
, любое значение (значения), переданное члену перечисления, будет передано в эти методы. Пример см. в разделе Planet.
Примечание
Метод __new__()
, если он определен, используется при создании членов Enum; затем он заменяется методом __new__()
Enum, который используется после создания класса для поиска существующих членов. Более подробную информацию см. в разделе Когда использовать __new__() против __init__().
Ограниченный подкласс Enum¶
Новый класс Enum
должен иметь один базовый класс перечисления, до одного конкретного типа данных и столько классов-миксинов, сколько потребуется. Порядок этих базовых классов следующий:
class EnumName([mix-in, ...,] [data-type,] base-enum):
pass
Кроме того, подклассификация перечисления разрешена только в том случае, если перечисление не определяет никаких членов. Так что это запрещено:
>>> class MoreColor(Color):
... PINK = 17
...
Traceback (most recent call last):
...
TypeError: <enum 'MoreColor'> cannot extend <enum 'Color'>
Но это разрешено:
>>> class Foo(Enum):
... def some_behavior(self):
... pass
...
>>> class Bar(Foo):
... HAPPY = 1
... SAD = 2
...
Разрешение подклассификации перечислений, определяющих члены, привело бы к нарушению некоторых важных инвариантов типов и экземпляров. С другой стороны, имеет смысл разрешить разделять некоторое общее поведение между группой перечислений. (Пример см. в OrderedEnum).
Поддержка классов данных¶
При наследовании от dataclass
в __repr__()
опускается имя наследуемого класса. Например:
>>> from dataclasses import dataclass, field
>>> @dataclass
... class CreatureDataMixin:
... size: str
... legs: int
... tail: bool = field(repr=False, default=True)
...
>>> class Creature(CreatureDataMixin, Enum):
... BEETLE = 'small', 6
... DOG = 'medium', 4
...
>>> Creature.DOG
<Creature.DOG: size='medium', legs=4>
Используйте аргумент dataclass()
repr=False
, чтобы использовать стандартный repr()
.
Изменено в версии 3.12: В области значений отображаются только поля класса данных, а не имя класса данных.
Примечание
Добавление декоратора dataclass()
к Enum
и его подклассам не поддерживается. Это не вызовет ошибок, но приведет к очень странным результатам во время выполнения, например, члены будут равны друг другу:
>>> @dataclass # don't do this: it does not make any sense
... class Color(Enum):
... RED = 1
... BLUE = 2
...
>>> Color.RED is Color.BLUE
False
>>> Color.RED == Color.BLUE # problem is here: they should not be equal
True
Маринование¶
Перечисления могут быть маринованными и немаринованными:
>>> from test.test_enum import Fruit
>>> from pickle import dumps, loads
>>> Fruit.TOMATO is loads(dumps(Fruit.TOMATO))
True
Для пикировки действуют обычные ограничения: пикируемые перечисления должны быть определены на верхнем уровне модуля, так как для распикировки требуется, чтобы они были импортируемы из этого модуля.
Примечание
С помощью протокола pickle версии 4 можно легко собирать перечисления, вложенные в другие классы.
Можно изменить способ маринования/немаринования членов перечисления, определив __reduce_ex__()
в классе перечисления. По умолчанию используется метод by-value, но перечисления со сложными значениями могут захотеть использовать by-name:
>>> import enum
>>> class MyEnum(enum.Enum):
... __reduce_ex__ = enum.pickle_by_enum_name
Примечание
Использовать by-name для флагов не рекомендуется, так как безымянные псевдонимы не будут распаковываться.
Функциональный API¶
Класс Enum
является вызываемым и предоставляет следующий функциональный API:
>>> Animal = Enum('Animal', 'ANT BEE CAT DOG')
>>> Animal
<enum 'Animal'>
>>> Animal.ANT
<Animal.ANT: 1>
>>> list(Animal)
[<Animal.ANT: 1>, <Animal.BEE: 2>, <Animal.CAT: 3>, <Animal.DOG: 4>]
Семантика этого API похожа на namedtuple
. Первым аргументом вызова Enum
является имя перечисления.
Второй аргумент - это источник имен членов перечисления. Это может быть строка имен, разделенная пробелами, последовательность имен, последовательность 2-кортежей с парами ключ/значение или отображение (например, словарь) имен на значения. Последние два варианта позволяют присваивать перечислениям произвольные значения; остальные автоматически присваивают возрастающие целые числа, начиная с 1 (используйте параметр start
, чтобы указать другое начальное значение). Возвращается новый класс, производный от Enum
. Другими словами, приведенное выше присвоение Animal
эквивалентно:
>>> class Animal(Enum):
... ANT = 1
... BEE = 2
... CAT = 3
... DOG = 4
...
Причина использования по умолчанию 1
в качестве начального числа, а не 0
заключается в том, что 0
является False
в булевом смысле, но по умолчанию все члены перечисления оцениваются в True
.
Очистка перечислений, созданных с помощью функционального API, может оказаться непростой задачей, поскольку детали реализации стека кадров используются для того, чтобы попытаться определить, в каком модуле создается перечисление (например, это не сработает, если вы используете служебную функцию в отдельном модуле, а также может не сработать на IronPython или Jython). Решением является явное указание имени модуля следующим образом:
>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__)
Предупреждение
Если module
не предоставлен, и Enum не может определить, что это такое, новые члены Enum не будут распикированы; чтобы сохранить ошибки ближе к источнику, распикирование будет отключено.
Новый протокол pickle 4 также, в некоторых обстоятельствах, полагается на то, что __qualname__
будет установлен в то место, где pickle сможет найти класс. Например, если класс был доступен в классе SomeData в глобальной области видимости:
>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', qualname='SomeData.Animal')
Полная подпись:
Enum(
value='NewEnumName',
names=<...>,
*,
module='...',
qualname='...',
type=<mixed-in class>,
start=1,
)
значение: То, что новый класс перечисления будет записывать в качестве своего имени.
names: Члены перечисления. Это может быть строка, разделенная пробелами или запятыми (значения будут начинаться с 1, если не указано иное):
'RED GREEN BLUE' | 'RED,GREEN,BLUE' | 'RED, GREEN, BLUE'
или итератор имен:
['RED', 'GREEN', 'BLUE']
или итератор пар (имя, значение):
[('CYAN', 4), ('MAGENTA', 5), ('YELLOW', 6)]
или отображение:
{'CHARTREUSE': 7, 'SEA_GREEN': 11, 'ROSEMARY': 42}
module: имя модуля, в котором можно найти новый класс перечисления.
qualname: где в модуле можно найти новый класс перечисления.
type: тип для добавления в новый класс перечисления.
start: число, с которого следует начинать отсчет, если переданы только имена.
Изменено в версии 3.5: Был добавлен параметр start.
Производные перечисления¶
IntEnum¶
Первая предоставленная вариация Enum
также является подклассом int
. Члены IntEnum
можно сравнивать с целыми числами; в свою очередь, целые перечисления разных типов также можно сравнивать друг с другом:
>>> from enum import IntEnum
>>> class Shape(IntEnum):
... CIRCLE = 1
... SQUARE = 2
...
>>> class Request(IntEnum):
... POST = 1
... GET = 2
...
>>> Shape == 1
False
>>> Shape.CIRCLE == 1
True
>>> Shape.CIRCLE == Request.POST
True
Однако их все равно нельзя сравнивать со стандартными перечислениями Enum
:
>>> class Shape(IntEnum):
... CIRCLE = 1
... SQUARE = 2
...
>>> class Color(Enum):
... RED = 1
... GREEN = 2
...
>>> Shape.CIRCLE == Color.RED
False
Значения IntEnum
ведут себя как целые числа в других отношениях, которые вы ожидаете:
>>> int(Shape.CIRCLE)
1
>>> ['a', 'b', 'c'][Shape.CIRCLE]
'b'
>>> [i for i in range(Shape.SQUARE)]
[0, 1]
StrEnum¶
Второй вариант Enum
, который предоставляется, также является подклассом str
. Члены StrEnum
можно сравнивать со строками; кроме того, строковые перечисления разных типов можно сравнивать друг с другом.
Added in version 3.11.
IntFlag¶
Следующая вариация Enum
, IntFlag
, также основана на int
. Разница в том, что члены IntFlag
можно объединять с помощью побитовых операторов (&, |, ^, ~), и результатом будет член IntFlag
, если это возможно. Как и IntEnum
, члены IntFlag
также являются целыми числами и могут использоваться везде, где используется int
.
Примечание
Любая операция над членом IntFlag
, кроме побитовых операций, приводит к потере членства IntFlag
.
При побитовых операциях, приводящих к недопустимым значениям IntFlag
, членство в IntFlag
теряется. Подробности см. в разделе FlagBoundary
.
Added in version 3.6.
Изменено в версии 3.11.
Образец IntFlag
класса:
>>> from enum import IntFlag
>>> class Perm(IntFlag):
... R = 4
... W = 2
... X = 1
...
>>> Perm.R | Perm.W
<Perm.R|W: 6>
>>> Perm.R + Perm.W
6
>>> RW = Perm.R | Perm.W
>>> Perm.R in RW
True
Можно также назвать комбинации:
>>> class Perm(IntFlag):
... R = 4
... W = 2
... X = 1
... RWX = 7
...
>>> Perm.RWX
<Perm.RWX: 7>
>>> ~Perm.RWX
<Perm: 0>
>>> Perm(7)
<Perm.RWX: 7>
Примечание
Именованные комбинации считаются псевдонимами. Псевдонимы не отображаются во время итерации, но могут быть возвращены при поиске по значению.
Изменено в версии 3.11.
Еще одно важное различие между IntFlag
и Enum
заключается в том, что если флаги не установлены (значение равно 0), то его булева оценка равна False
:
>>> Perm.R & Perm.X
<Perm: 0>
>>> bool(Perm.R & Perm.X)
False
Поскольку члены IntFlag
также являются подклассами int
, они могут быть объединены с ними (но могут потерять членство в IntFlag
):
>>> Perm.X | 4
<Perm.R|X: 5>
>>> Perm.X + 8
9
Примечание
Оператор отрицания, ~
, всегда возвращает член IntFlag
с положительным значением:
>>> (~Perm.X).value == (Perm.R|Perm.W).value == 6
True
Члены IntFlag
также можно итерировать:
>>> list(RW)
[<Perm.R: 4>, <Perm.W: 2>]
Added in version 3.11.
Флаг¶
Последним вариантом является Flag
. Как и IntFlag
, члены Flag
можно объединять с помощью побитовых операторов (&, |, ^, ~). В отличие от IntFlag
, их нельзя объединять и сравнивать с другими перечислениями Flag
, а также int
. Хотя можно указывать значения напрямую, рекомендуется использовать auto
в качестве значения и позволить Flag
выбрать подходящее значение.
Added in version 3.6.
Как и в случае с IntFlag
, если комбинация членов Flag
не приводит к установке флагов, то булева оценка будет False
:
>>> from enum import Flag, auto
>>> class Color(Flag):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> Color.RED & Color.GREEN
<Color: 0>
>>> bool(Color.RED & Color.GREEN)
False
Отдельные флаги должны иметь значения, равные степени двойки (1, 2, 4, 8, …), а комбинации флагов - нет:
>>> class Color(Flag):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
... WHITE = RED | BLUE | GREEN
...
>>> Color.WHITE
<Color.WHITE: 7>
Присвоение имени условию «флаги не установлены» не меняет его булевого значения:
>>> class Color(Flag):
... BLACK = 0
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> Color.BLACK
<Color.BLACK: 0>
>>> bool(Color.BLACK)
False
Члены Flag
также можно итерировать:
>>> purple = Color.RED | Color.BLUE
>>> list(purple)
[<Color.RED: 1>, <Color.BLUE: 2>]
Added in version 3.11.
Примечание
Для большинства нового кода настоятельно рекомендуется использовать Enum
и Flag
, поскольку IntEnum
и IntFlag
нарушают некоторые семантические обещания перечислений (будучи сравнимыми с целыми числами и, следовательно, транзитивными по отношению к другим несвязанным перечислениям). IntEnum
и IntFlag
следует использовать только в тех случаях, когда Enum
и Flag
не подходят; например, когда целочисленные константы заменяются перечислениями, или для совместимости с другими системами.
Другие¶
Хотя IntEnum
является частью модуля enum
, его было бы очень просто реализовать независимо:
class IntEnum(int, ReprEnum): # or Enum instead of ReprEnum
pass
Это демонстрирует, как можно определить аналогичные производные перечисления; например, FloatEnum
, в которое вместо int
добавляется float
.
Некоторые правила:
При подклассификации
Enum
смешиваемые типы должны появляться перед самим классомEnum
в последовательности баз, как в примереIntEnum
выше.Смешиваемые типы должны быть подклассифицируемыми. Например,
bool
иrange
не являются подклассами и будут вызывать ошибку при создании Enum, если будут использоваться в качестве смешиваемого типа.Хотя
Enum
может иметь члены любого типа, при добавлении дополнительного типа все члены должны иметь значения этого типа, например,int
выше. Это ограничение не распространяется на микс-ины, которые добавляют только методы и не указывают другой тип.При использовании другого типа данных атрибут
value
не является тем же самым, что и сам член перечисления, хотя они эквивалентны и будут сравниваться одинаково.data type
- это миксин, определяющий__new__()
, илиdataclass
.Форматирование в стиле %:
%s
и%r
вызывают__str__()
и__repr__()
классаEnum
соответственно; другие коды (например,%i
или%h
для IntEnum) рассматривают член перечисления как его смешанный тип.Formatted string literals,
str.format()
иformat()
будут использовать метод перечисления__str__()
.
Когда использовать __new__()
против __init__()
¶
__new__()
следует использовать всякий раз, когда вы хотите настроить фактическое значение члена Enum
. Любые другие модификации можно поместить в __new__()
или __init__()
, при этом предпочтительнее использовать __init__()
.
Например, если вы хотите передать в конструктор несколько элементов, но только один из них должен быть значением:
>>> class Coordinate(bytes, Enum):
... """
... Coordinate with binary codes that can be indexed by the int code.
... """
... def __new__(cls, value, label, unit):
... obj = bytes.__new__(cls, [value])
... obj._value_ = value
... obj.label = label
... obj.unit = unit
... return obj
... PX = (0, 'P.X', 'km')
... PY = (1, 'P.Y', 'km')
... VX = (2, 'V.X', 'km/s')
... VY = (3, 'V.Y', 'km/s')
...
>>> print(Coordinate['PY'])
Coordinate.PY
>>> print(Coordinate(3))
Coordinate.VY
Предупреждение
*Не вызывайте super().__new__()
, так как будет найден только __new__
; вместо этого используйте тип данных напрямую.
Тонкости¶
Поддерживаемые имена __dunder__
¶
__members__
- это упорядоченное отображение элементов member_name
:member
, доступное только для чтения. Оно доступно только для класса.
__new__()
, если он указан, должен создавать и возвращать члены перечисления; также очень хорошей идеей будет установить соответствующее значение _value_
. После того как все члены будут созданы, эта функция больше не используется.
Поддерживаемые имена _sunder_
¶
_name_
– имя участника_value_
– значение члена; может быть задано в__new__
_missing_()
– функция поиска, используемая, когда значение не найдено; может быть переопределена_ignore_
– список имен, либо в видеlist
, либо в видеstr
, которые не будут преобразованы в члены и будут удалены из конечного класса_generate_next_value_()
– используется для получения соответствующего значения для члена перечисления; может быть переопределено_add_alias_()
– добавляет новое имя в качестве псевдонима к существующему члену._add_value_alias_()
– добавляет новое значение в качестве псевдонима к существующему члену. Пример см. в разделе MultiValueEnum.Примечание
Для стандартных классов
Enum
следующим выбирается наибольшее увиденное значение, увеличенное на единицу.Для классов
Flag
в качестве следующего значения будет выбрана наибольшая степень двойки.Изменено в версии 3.13: В предыдущих версиях вместо наибольшего значения использовалось последнее замеченное значение.
Added in version 3.6: _missing_
, _order_
, _generate_next_value_
Added in version 3.7: _ignore_
Added in version 3.13: _add_alias_
, _add_value_alias_
Для синхронизации кода Python 2 и Python 3 можно указать атрибут _order_
. Он будет сверен с фактическим порядком перечисления и выдаст ошибку, если они не совпадают:
>>> class Color(Enum):
... _order_ = 'RED GREEN BLUE'
... RED = 1
... BLUE = 3
... GREEN = 2
...
Traceback (most recent call last):
...
TypeError: member order does not match _order_:
['RED', 'BLUE', 'GREEN']
['RED', 'GREEN', 'BLUE']
Примечание
В коде Python 2 атрибут _order_
необходим, поскольку порядок определения теряется до того, как он может быть записан.
_Private__names¶
Private names не преобразуются в члены перечисления, а остаются обычными атрибутами.
Изменено в версии 3.11.
Enum
тип члена¶
Члены перечисления являются экземплярами своего класса перечисления и обычно доступны как EnumClass.member
. В некоторых ситуациях, например, при написании пользовательского поведения перечисления, возможность доступа к одному члену непосредственно из другого является полезной и поддерживается; однако, чтобы избежать столкновения имен членов и атрибутов/методов из смешанных классов, настоятельно рекомендуется использовать имена в верхнем регистре.
Изменено в версии 3.5.
Создание членов, смешанных с другими типами данных¶
При подклассификации других типов данных, таких как int
или str
, с помощью Enum
, все значения после =
передаются в конструктор этого типа данных. Например:
>>> class MyEnum(IntEnum): # help(int) -> int(x, base=10) -> integer
... example = '11', 16 # so x='11' and base=16
...
>>> MyEnum.example.value # and hex(11) is...
17
Булево значение классов и членов Enum
¶
Классы перечислений, которые смешиваются с типами, отличными от Enum
(такими как int
, str
и т. д.), оцениваются в соответствии с правилами смешанного типа; в противном случае все члены оцениваются как True
. Чтобы сделать оценку булевых значений в вашем собственном перечислении зависящей от значения члена, добавьте в класс следующее:
def __bool__(self):
return bool(self.value)
Enum
классы с методами¶
Если вы дадите своему подклассу enum дополнительные методы, как в классе Planet ниже, эти методы будут отображаться в dir()
члена, но не в классе:
>>> dir(Planet)
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH)
['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value']
Объединение членов Flag
¶
Итерация по комбинации членов Flag
вернет только те члены, которые состоят из одного бита:
>>> class Color(Flag):
... RED = auto()
... GREEN = auto()
... BLUE = auto()
... MAGENTA = RED | BLUE
... YELLOW = RED | GREEN
... CYAN = GREEN | BLUE
...
>>> Color(3) # named combination
<Color.YELLOW: 3>
>>> Color(7) # not named combination
<Color.RED|GREEN|BLUE: 7>
Flag
и IntFlag
миниатюры¶
В качестве примера мы используем следующий фрагмент:
>>> class Color(IntFlag):
... BLACK = 0
... RED = 1
... GREEN = 2
... BLUE = 4
... PURPLE = RED | BLUE
... WHITE = RED | GREEN | BLUE
...
следующие утверждения верны:
Однобитные флаги являются каноническими
многобитные и нуль-битные флаги являются псевдонимами
во время итерации возвращаются только канонические флаги:
>>> list(Color.WHITE) [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
Отрицание флага или набора флагов возвращает новый флаг/набор флагов с соответствующим положительным целым значением:
>>> Color.BLUE <Color.BLUE: 4> >>> ~Color.BLUE <Color.RED|GREEN: 3>
имена псевдофлагов строятся из имен их членов:
>>> (Color.RED | Color.GREEN).name 'RED|GREEN'
Мультибитные флаги, они же псевдонимы, могут быть возвращены из операций:
>>> Color.RED | Color.BLUE <Color.PURPLE: 5> >>> Color(7) # or Color(-1) <Color.WHITE: 7> >>> Color(0) <Color.BLACK: 0>
проверка принадлежности/содержания: флаги с нулевым значением всегда считаются содержащимися:
>>> Color.BLACK in Color.WHITE True
иначе, только если все биты одного флага находятся в другом флаге, будет возвращено True:
>>> Color.PURPLE in Color.WHITE True >>> Color.GREEN in Color.PURPLE False
Существует новый пограничный механизм, который управляет тем, как обрабатываются биты, выходящие за пределы диапазона / недействительные биты: STRICT
, CONFORM
, EJECT
и KEEP
:
STRICT –> вызывает исключение при представлении недопустимых значений
CONFORM –> отбрасывает все недопустимые биты
EJECT –> теряет статус флага и становится обычным int с заданным значением
KEEP –> сохранить дополнительные биты
сохраняет статус флага и дополнительные биты
Лишние биты не отображаются в итерации
лишние биты отображаются в repr() и str()
По умолчанию для Flag используется значение STRICT
, для IntFlag
- EJECT
, а для _convert_
- KEEP
(см. ssl.Options
для примера, когда требуется KEEP
).
Чем отличаются Enums и Flags?¶
Enums имеет собственный метакласс, который влияет на многие аспекты как производных Enum
классов, так и их экземпляров (членов).
Классы Enum¶
Метакласс EnumType
отвечает за предоставление __contains__()
, __dir__()
, __iter__()
и других методов, которые позволяют делать с классом Enum
вещи, которые не удается сделать с обычным классом, таким как list(Color)
или some_enum_var in Color
. EnumType
отвечает за обеспечение корректности различных других методов конечного класса Enum
(таких как __new__()
, __getnewargs__()
, __str__()
и __repr__()
).
Классы флагов¶
Флаги имеют расширенное представление о псевдонимии: чтобы быть каноническим, значение флага должно быть равным двум мощным значениям, а не дублирующим именем. Таким образом, в дополнение к определению псевдонима Enum
, псевдонимом считается флаг без значения (например, 0
) или с более чем одним значением из двух (например, 3
).
Члены Enum (они же экземпляры)¶
Самое интересное в членах перечисления - это то, что они являются синглтонами. EnumType
создает их все при создании самого класса enum, а затем устанавливает пользовательский __new__()
, чтобы гарантировать, что новые члены никогда не будут инстанцированы, возвращая только существующие экземпляры членов.
Члены флага¶
Члены флага можно итерировать, как и в классе Flag
, при этом будут возвращены только канонические члены. Например:
>>> list(Color)
[<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
(Обратите внимание, что BLACK
, PURPLE
и WHITE
не отображаются).
Инвертирование члена флага возвращает соответствующее положительное, а не отрицательное значение - например:
>>> ~Color.RED
<Color.GREEN|BLUE: 6>
Члены флага имеют длину, соответствующую количеству содержащихся в них двоек. Например:
>>> len(Color.PURPLE)
2
Поваренная книга Enum¶
Хотя ожидается, что Enum
, IntEnum
, StrEnum
, Flag
и IntFlag
покроют большинство случаев использования, они не могут охватить их все. Здесь приведены рецепты некоторых различных типов перечислений, которые можно использовать непосредственно или в качестве примеров для создания собственных.
Пропуск значений¶
Во многих случаях использования неважно, каково фактическое значение перечисления. Существует несколько способов определения такого типа простых перечислений:
используйте экземпляры
auto
для значенияиспользовать экземпляры
object
в качестве значенияиспользовать описательную строку в качестве значения
использовать кортеж в качестве значения и пользовательский
__new__()
для замены кортежа на значениеint
Использование любого из этих методов означает, что эти значения не важны, а также позволяет добавлять, удалять или изменять порядок членов без необходимости перенумеровывать оставшиеся члены.
Используя auto
¶
Использование auto
будет выглядеть так:
>>> class Color(Enum):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> Color.GREEN
<Color.GREEN: 3>
Используя object
¶
Использование object
будет выглядеть так:
>>> class Color(Enum):
... RED = object()
... GREEN = object()
... BLUE = object()
...
>>> Color.GREEN
<Color.GREEN: <object object at 0x...>>
Это также хороший пример того, почему вы можете захотеть написать свой собственный __repr__()
:
>>> class Color(Enum):
... RED = object()
... GREEN = object()
... BLUE = object()
... def __repr__(self):
... return "<%s.%s>" % (self.__class__.__name__, self._name_)
...
>>> Color.GREEN
<Color.GREEN>
Использование описательной строки¶
Использование строки в качестве значения будет выглядеть так:
>>> class Color(Enum):
... RED = 'stop'
... GREEN = 'go'
... BLUE = 'too fast!'
...
>>> Color.GREEN
<Color.GREEN: 'go'>
Использование пользовательского __new__()
¶
Использование автоматической нумерации __new__()
будет выглядеть так:
>>> class AutoNumber(Enum):
... def __new__(cls):
... value = len(cls.__members__) + 1
... obj = object.__new__(cls)
... obj._value_ = value
... return obj
...
>>> class Color(AutoNumber):
... RED = ()
... GREEN = ()
... BLUE = ()
...
>>> Color.GREEN
<Color.GREEN: 2>
Чтобы сделать более универсальный AutoNumber
, добавьте *args
к сигнатуре:
>>> class AutoNumber(Enum):
... def __new__(cls, *args): # this is the only change from above
... value = len(cls.__members__) + 1
... obj = object.__new__(cls)
... obj._value_ = value
... return obj
...
Затем, когда вы наследуете от AutoNumber
, вы можете написать свой собственный __init__
для обработки любых дополнительных аргументов:
>>> class Swatch(AutoNumber):
... def __init__(self, pantone='unknown'):
... self.pantone = pantone
... AUBURN = '3497'
... SEA_GREEN = '1246'
... BLEACHED_CORAL = () # New color, no Pantone code yet!
...
>>> Swatch.SEA_GREEN
<Swatch.SEA_GREEN: 2>
>>> Swatch.SEA_GREEN.pantone
'1246'
>>> Swatch.BLEACHED_CORAL.pantone
'unknown'
Примечание
Метод __new__()
, если он определен, используется при создании членов Enum; затем он заменяется методом __new__()
Enum, который используется после создания класса для поиска существующих членов.
Предупреждение
Не вызывайте super().__new__()
, так как будет найден только __new__
; вместо этого используйте тип данных напрямую - например:
obj = int.__new__(cls, value)
OrderedEnum¶
Упорядоченное перечисление, которое не основано на IntEnum
и поэтому сохраняет обычные инварианты Enum
(например, не сравнивается с другими перечислениями):
>>> class OrderedEnum(Enum):
... def __ge__(self, other):
... if self.__class__ is other.__class__:
... return self.value >= other.value
... return NotImplemented
... def __gt__(self, other):
... if self.__class__ is other.__class__:
... return self.value > other.value
... return NotImplemented
... def __le__(self, other):
... if self.__class__ is other.__class__:
... return self.value <= other.value
... return NotImplemented
... def __lt__(self, other):
... if self.__class__ is other.__class__:
... return self.value < other.value
... return NotImplemented
...
>>> class Grade(OrderedEnum):
... A = 5
... B = 4
... C = 3
... D = 2
... F = 1
...
>>> Grade.C < Grade.A
True
DuplicateFreeEnum¶
Вызывает ошибку, если найдено дублирующее значение члена, вместо того чтобы создать псевдоним:
>>> class DuplicateFreeEnum(Enum):
... def __init__(self, *args):
... cls = self.__class__
... if any(self.value == e.value for e in cls):
... a = self.name
... e = cls(self.value).name
... raise ValueError(
... "aliases not allowed in DuplicateFreeEnum: %r --> %r"
... % (a, e))
...
>>> class Color(DuplicateFreeEnum):
... RED = 1
... GREEN = 2
... BLUE = 3
... GRENE = 2
...
Traceback (most recent call last):
...
ValueError: aliases not allowed in DuplicateFreeEnum: 'GRENE' --> 'GREEN'
Примечание
Это полезный пример подклассификации Enum для добавления или изменения других поведений, а также запрета псевдонимов. Если единственным желаемым изменением является запрет псевдонимов, вместо него можно использовать декоратор unique()
.
MultiValueEnum¶
Поддерживает более одного значения для каждого члена:
>>> class MultiValueEnum(Enum):
... def __new__(cls, value, *values):
... self = object.__new__(cls)
... self._value_ = value
... for v in values:
... self._add_value_alias_(v)
... return self
...
>>> class DType(MultiValueEnum):
... float32 = 'f', 8
... double64 = 'd', 9
...
>>> DType('f')
<DType.float32: 'f'>
>>> DType(9)
<DType.double64: 'd'>
Планета¶
Если определено значение __new__()
или __init__()
, то значение члена перечисления будет передано в эти методы:
>>> class Planet(Enum):
... MERCURY = (3.303e+23, 2.4397e6)
... VENUS = (4.869e+24, 6.0518e6)
... EARTH = (5.976e+24, 6.37814e6)
... MARS = (6.421e+23, 3.3972e6)
... JUPITER = (1.9e+27, 7.1492e7)
... SATURN = (5.688e+26, 6.0268e7)
... URANUS = (8.686e+25, 2.5559e7)
... NEPTUNE = (1.024e+26, 2.4746e7)
... def __init__(self, mass, radius):
... self.mass = mass # in kilograms
... self.radius = radius # in meters
... @property
... def surface_gravity(self):
... # universal gravitational constant (m3 kg-1 s-2)
... G = 6.67300E-11
... return G * self.mass / (self.radius * self.radius)
...
>>> Planet.EARTH.value
(5.976e+24, 6378140.0)
>>> Planet.EARTH.surface_gravity
9.802652743337129
TimePeriod¶
Пример использования атрибута _ignore_
:
>>> from datetime import timedelta
>>> class Period(timedelta, Enum):
... "different lengths of time"
... _ignore_ = 'Period i'
... Period = vars()
... for i in range(367):
... Period['day_%d' % i] = i
...
>>> list(Period)[:2]
[<Period.day_0: datetime.timedelta(0)>, <Period.day_1: datetime.timedelta(days=1)>]
>>> list(Period)[-2:]
[<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>]
Подклассификация EnumType¶
Хотя большинство потребностей в перечислениях можно удовлетворить путем настройки подклассов Enum
с помощью декораторов класса или пользовательских функций, подкласс EnumType
может быть изменен, чтобы обеспечить другой опыт работы с перечислениями.