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