Python enum HOWTO |
![]() |
Добавил(а) microsin |
Класс Enum в Python [2] это набор символических имен, привязанных к уникальным значениям. Это подобно глобальным переменным, но также предоставляются repr(), группирование, type-safety и другие удобные фичи. Значения перечисления больше всего подходят для случая, когда переменная может иметь ограниченный набор выбираемых значений. Например, это могут быть дни недели: >>> from enum import Enum Или это могут быть основные цвета RGB: >>> from enum import Enum Как вы можете видеть, создание перечисления Enum это просто написание класса, который наследуется от самого Enum. Замечание по поводу использования прописных/строчных букв: поскольку перечисления используются для представления констант, то воизбежание проблем конфликта с именами переменных и методов настоятельно рекомендуется ИСПОЛЬЗОВАТЬ ВЕРХНИЙ РЕГИСТР для имен значений перечисления. В примерах как раз будет использоваться такой стиль именования. В зависимости от характера перечисления значение его члена может быть или не быть важным, но в любом случае это значение может быть использовано для получения соответствующего члена: >>> Weekday(3) < Weekday.WEDNESDAY: 3> Как вы можете видеть, функция repr() элемента перечисления покажет имя enum, имя члена enum и значение члена enum. Функция str() элемента перечисления покажет только имя enum и имя члена enum: >>> print(Weekday.THURSDAY) Weekday.THURSDAY Функция type() для элемента перечисления покажет, какому enum он принадлежит: >>> type(Weekday.MONDAY) < enum 'Weekday'> Элементы Enum имеют атрибут name, который просто содержит их имя: >>> print(Weekday.TUESDAY.name) TUESDAY Подобным образом есть атрибут value, который содержит значение элемента Enum: >>> Weekday.WEDNESDAY.value В отличие от многих языков программирования, которые обрабатывают перечисления чисто только как пары имя/значение, Python Enum может иметь добавленное поведение. Например, datetime.date содержит 2 метода для возврата недели: weekday() и isoweekday(). Они различаются тем, что один считает от 0 до 6, а другой от 1 до 7. Вместо того, чтобы отслеживать это самостоятельно, мы может добавить метод в перечисление Weekday, чтобы извлечь день из экземпляра date, и возвратить соответствующий элемент перечисления: @classmethod Полное определение Weekday enum теперь выглядит так: >>> class Weekday(Enum): Теперь мы можем определить, какой сегодня день! Смотрите: >>> from datetime import date Weekday.from_date(date.today()) < Weekday.TUESDAY: 2> Конечно, если вы попробуете это в какой-то другой день, то выведенное значение будет другим. Перечисление Weekday хорошо подойдет, если нашей переменной нужно значение только одного дня, но что если нам понадобится несколько? Возможно, что мы пишем функцию планирования дел в течение недели, и не хотим использовать список – тогда мы могли бы использовать другой тип Enum: >>> from enum import Flag Здесь мы поменяли две вещи: унаследовали класс от Flag, и все значения элементов флагов сделали степенями двойки. Точно так же, как и у оригинального варианта перечисления Weekday, у нас может быть одно выбранное значение для переменной: >>> first_week_day = Weekday.MONDAY first_week_day < Weekday.MONDAY: 1> Но Flag также позволяет комбинировать в одной переменной несколько значений членов: >>> weekend = Weekday.SATURDAY | Weekday.SUNDAY Вы даже можете сделать итерацию по переменной Flag: >>> for day in weekend: Вот так мы можем настроить план важных дел: >>> chores_for_ethan = { Пример функции, которая покажет, что запланировано на указанный день: >>> def show_chores(chores, day): В тех случаях, когда реальные значения элементов Flag не имеют значения, можно сэкономить усилия, используя auto() для значений элементов: >>> from enum import auto [Программный доступ к элементам перечисления и их атрибутам] Иногда полезно программно обращаться к члена в перечислениях (например это ситуации, где Color.RED не то, что работает, потому что точный цвет неизвестен во время написания программы). Enum позволяет осуществлять такой доступ: >>> Color(1) < Color.RED: 1> >> Color(3) < Color.BLUE: 3> Если вы хотите обратиться к элементам перечисления по имени, то используйте: >>> Color['RED'] < Color.RED: 1> Если у вас есть элемент перечисления, в необходимо его имя или значение: >>> member = Color.RED [Дублирование элементов и значений enum] Недопустимо давать двум элементам перечисления одинаковые имена: >>> class Shape(Enum): Однако элементы перечисления могут иметь имена, связанные с другим именем. Если дать элементам A и B одинаковое значение, и A был определен первым, то B становится псевдонимом (alias) для элемента A. Выборка по имени A возвратит элемент A. Выборка по имени B также возвратит элемент A: >>> class Shape(Enum): Замечание: попытка создать элемент с таким же именем, что и уже определенный атрибут (другой элемент, метод, и т. д.), или попытка создать атрибут с таким же именем, как у элемента - все это не допускается. [Гарантирование уникальности значений перечисления] По умолчанию перечисления позволяют несколько имен как псевдонимы одного и того же значения. Но когда такое поведение нежелательно, вы можете использовать декоратор unique(): >>> from enum import Enum, unique [Использование автоматических значений] Если точное значение не важно, то вы можете использовать auto: >>> from enum import Enum, auto Значения, выбранные функцией _generate_next_value_(), которые могут быть переназначены: >>> class AutoName(Enum): Обратите внимание, что метод _generate_next_value_() должен быть определен перед любыми элементами. [Итерация] Итерация по элементам enum не предоставляет псевдонимы: >>> list(Shape) [< Shape.SQUARE: 2>, < Shape.DIAMOND: 1>, < Shape.CIRCLE: 3>] Обратите внимание, что не показаны псевдонимы Shape.ALIAS_FOR_SQUARE и Weekday.WEEKEND. Специальный атрибут __members__ это упорядоченное read-only отображение имен на элементы перечисления. Он включает все имена, определенные в перечислении, в том числе и псевдонимы: >>> for name, member in Shape.__members__.items(): Атрибут __members__ может использовать для детализированного программного доступа к элементам перечисления. Например, вот так можно найти все псевдонимы: >>> [name for name, member in Shape.__members__.items() if member.name != name] ['ALIAS_FOR_SQUARE'] Обратите внимание, что псевдонимы для флагов включают значения с несколькими установленными флагами, такие как 3, и без установленных флагов, т. е. 0. [Сравнения] Элементы перечисления можно сравнивать по идентичности: >>> Color.RED is Color.RED Упорядоченные сравнения между значениями перечислений не поддерживаются. Элементы перечисления не являются целыми числами (однако см. далее 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 Сравнения с чем-то, не относящимся к значениям перечисления, всегда покажет не эквивалентность (и опять-таки, IntEnum было разработано с полностью другим поведением, о чем дальше): >>> Color.BLUE == 2 Предупреждение: можно перезагрузить модули – если перезагруженный модуль содержит перечисления, то они будут созданы заново, и новые элементы могут не быть идентичны/эквивалентны оригинальным элементам. [Разрешенные элементы и атрибуты перечислений] Большинство примеров выше используют целые числа для значений перечисления. Использование целых чисел это краткий и удобный способ (и он предоставляется по умолчанию в Functional API), но он не строго соблюдается. В подавляющем большинстве случаев использования не важно, какое фактическое значение перечисления. Но если значение важно, то перечисления могут иметь произвольные значения. Перечисления являются в Python классами, и они могут иметь методы, как обычно. Если у нас есть такое перечисление: >>> class Mood(Enum): .. тогда: >>> Mood.favorite_mood() < Mood.HAPPY: 3> Правила для дозволенного следующие: имена, которые начинаются и заканчиваются одним символом подчеркивания, зарезервированы перечислением и не могут использоваться; все другие атрибуты, определенные в перечислении, станут членами этого перечисления, за исключением специальных методов (__str__(), __add__(), и т. п.), дескрипторов (методы тоже являются дескрипторами), и имен переменных, перечисленных в _ignore_. Замечание: если ваше перечисление определяет __new__() и/или __init__(), то любое значение (значения) указанные для элементов enum будут переданы в эти методы. В качестве примера см. класс Planet. Обратите внимание, что метод __new__(), если определен, используется во время создания элементов Enum; затем он заменяется новым Enum-методом __new__(), который используется после создания класса для поиска существующих элементов. Для дополнительной информации см. "Когда использовать __new__(), а когда __init__()". [Ограничение создания субклассов Enum] Новый класс Enum должен иметь один базовый перечисления, до одного конкретного типа данных, и столько смешанных, основанных на объектах классов (object-based mixin classes) сколько необходимо. Порядок этих базовых классов: class EnumName([mix-in, ...,] [data-type,] base-enum): pass Также создание субкласса перечисления разрешено только в том случае, когда перечисление не определяет членов. Таким образом, это запрещено: >>> class MoreColor(Color): Однако это разрешено: >>> class Foo(Enum): Разрешение субклассов перечислений, которые определяют элементы, приведет к нарушению некоторых важных инвариантов типов и экземпляров. С другой стороны, имеет смысл разрешить совместное использование некоторого общего поведения группой перечислений (см. пример в OrderedEnum). [Поддержка dataclass] При наследовании из dataclass функция __repr__() опускает наследуемое имя класса. Например: >>> from dataclasses import dataclass, field Используйте аргумент repr=False для dataclass(), чтобы использовать стандартную repr(). Изменено в версии 3.12: в области значений отображаются только поля dataclass, а не имя dataclass. Замечание: добавление декоратора dataclass() для Enum и его субклассов не поддерживается. Это не вызовет никаких ошибок, но приведет к очень странным результатам runtime, например, если элементы равны друг другу: >>> @dataclass # не делайте так, это бессмысленно [Pickling] Перечисления могут быть pickled и unpickled [4]. >>> from test.test_enum import Fruit Применяются обычные ограничения для pickling: picklable-перечисления должны быть определены на верхнем уровне модуля, поскольку unpickling требует, чтобы они могли быть импортированы из этого модуля. Замечание: с pickle-протоколом версии 4 есть возможность просто делать pickle перечислений, вложенных другие классы. Можно изменить, как элементы enum будут pickled/unpickled, путем определения __reduce_ex__() в классе перечисления. Метод по умолчанию by-value (по значению), однако перечисления со сложными значениями могут захотеть вариант by-name (по имени): >>> import enum Замечание: вариант by-name для флагов не рекомендуется, поскольку не именованные псевдонимы (unnamed aliases) не пройдут операцию unpickle. Класс Enum является callable, предоставляя функциональное API: >>> Animal = Enum('Animal', 'ANT BEE CAT DOG') Семантика этого API напоминает именованный кортеж (namedtuple). Первым аргументом вызова Enum является имя перечисления. Второй аргумент это источник (source) имен элементов перечисления. Это может быть строка имен, где имена разделены пробелами, последовательность имен, последовательность 2-tuple с парами key/value, или mapping (отображение, т. е. словарь) имен на значения. Последние две опции позволяют назначать произвольные значения для перечислений; другие автоматически присваивают возрастающие целые числа, начиная с 1 (используйте параметр start для указания другого начального значения). Будет возвращен новый класс, унаследованный от Enum. Другими словами, показанное выше присваивание Animal эквивалентно следующему: >>> class Animal(Enum): Причина, по которой по умолчанию для начального значения используется 1, а не 0, состоит в том, что 0 это False в двоичном контексте, но по умолчанию все элементы перечисления оцениваются как True. Pickling перечислений, созданных функциональным API, может быть сложной, поскольку используются детали реализации фрейма стека, чтобы попытаться выяснить, в каком модуле создается перечисление (например, это потерпит неудачу, если вы используете служебную функцию в отдельном модуле, и также может не работать на IronPython или Jython). Решение состоит в явном указании имени модуля следующим образом: >>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__) Предупреждение: если модуль не предоставлен, то Enum не может определить, что это такое, новые элементы Enum будут unpicklable; чтобы держать ошибки ближе к источнику, pickling будет запрещен. Новый pickle-протокол 4, в некоторых обстоятельствах, полагается на установку __qualname__ в месте расположения, где pickle сможет найти класс. Например, если класс был сделан доступным в классе SomeData в глобальной области видимости: >>> Animal = Enum('Animal', 'ANT BEE CAT DOG', qualname='SomeData.Animal') Полная сигнатура следующая: Enum( value='NewEnumName', names=< ...>, *, module='...', qualname='...', type=< mixed-in class>, start=1, ) • value: это то, что новый класс enum запишет в качестве своего имени. • names: элементы enum. Это может быть строка с разделителем пробелом или запятой (значения начнутся с 1, если не указано что-то другое): 'RED GREEN BLUE' | 'RED,GREEN,BLUE' | 'RED, GREEN, BLUE' .. или итератор имен: ['RED', 'GREEN', 'BLUE'] .. или итератор пар (name, value): [('CYAN', 4), ('MAGENTA', 5), ('YELLOW', 6)] .. или отображение (mapping): {'CHARTREUSE': 7, 'SEA_GREEN': 11, 'ROSEMARY': 42} • module: имя модуля, где можно найти новый класс enum. • qualname: где в модуле можно найти новый класс enum. • type: тип для смешивания с новым классом enum. • start: число, от которого начнется отсчет при передаче только имен. Изменено в версии 3.5: был добавлен параметр start. [Унаследованные от перечислений классы] IntEnum. Первый предоставляемый вариант Enum также является подклассом от int. Элементы IntEnum можно сравнивать с целыми числами; по расширению, целочисленные перечисления различных типов можно сравнивать друг с другом: >>> from enum import IntEnum Однако, их все еще нельзя сравнивать со стандартными перечислениями Enum: >>> class Shape(IntEnum): Значения IntEnum ведут себя как целые числа и в других случаях, что вполне ожидаемо: >>> int(Shape.CIRCLE) StrEnum. Второй предоставляемый вариант Enum также является подклассом от str. Элементы StrEnum можно сравнивать со строками; по расширению, перечисления строк различных типов можно сравнивать друг с другом. Добавлено в версии 3.11. IntFlag. Следующий предоставляемый вариант Enum, IntFlag, также основан на int. Отличие элементов IntFlag в том, что их можно комбинировать битовыми операторами (&, |, ^, ~), и результат все еще будет элементом IntFlag, если это возможно. Наподобие IntEnum, элементы IntFlag также являются целыми числами и могут использоваться везде, где используется int. Замечание: любая операция над элементом IntFlag, кроме перечисленных выше битовых операций, приводит потере членства в IntFlag. Битовые операции, которые приводят к недопустимым значениям IntFlag, теряют членство IntFlag. Подробности см. в описании FlagBoundary [2]. Добавлено в версии 3.6. Изменено в версии 3.11. Пример класса IntFlag: >>> from enum import IntFlag Также можно именовать комбинации флагов: >>> class Perm(IntFlag): Замечание: именованные комбинации считаются псевдонимами. Псевдонимы не показываются в итерации, однако могут быть возвращены обращениями по значению (by-value lookup). Изменено в версии 3.11. Другое важное отличие IntFlag и Enum в том, что если ни один флаг элемента не установлен (значение 0), то его двоичная оценка равна False: >>> Perm.R & Perm.X < Perm: 0> Поскольку элементы IntFlag также являются субклассами от int, они могут комбинироваться с int (однако могут потерять членство в IntFlag): >>> Perm.X | 4 < Perm.R|X: 5>>>> Perm.X + 89 Замечание: оператор отрицания ~ всегда возвратит элемент IntFlag с положительным значением: >>> (~Perm.X).value == (Perm.R|Perm.W).value == 6 Элементы 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 Отдельные флаги должны иметь значения степеней двойки (1, 2, 4, 8, ...), в то время как их комбинации не будут значения степеней двойки: >>> class Color(Flag): Присвоение имени состояния "ни один флаг не установлен" не меняет его логического значения: >>> class Color(Flag): Элементы 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): # или Enum вместо ReprEnum pass Это демонстрирует, как как можно определить подобные производные перечисления; например, FloatEnum, который смешивается в float вместо int. Некоторые правила: 1. Когда выполняется наследование от Enum, mix-in типы должны появляться перед самим классом Enum в последовательности базовых классов, как в показанном выше примере IntEnum. 2. Mix-in типы должны быть способны к созданию производных классов (subclassable). Например, bool и range не являются subclassable, и будет выброшено исключение ошибки (throw error) при создании Enum, если они будут использоваться в качестве mix-in типа. 3. Хотя Enum может иметь элементы любого типа, после смешивания в дополнительный тип все члены должны иметь значения этого типа, например int как в примере выше. Это ограничение не применяется mix-in типам, в которых только добавляются методы, и не указывается другой тип. 4. При смешивании другого типа данных атрибут value не совпадает с самим элементом перечисления, хотя он эквивалентен и при сравнении окажется равным. 5. Тип данных (data type) это mixin, который определяет __new__() или dataclass. 6. Форматирование в %-стиле: %s и %r вызывают соответственно методы __str__() и __repr__() класса Enum; другие коды формата (такие как %i или %h для IntEnum) обрабатывают элемент enum как его mixed-in тип. 7. Строковые литералы форматирования str.format() и format() будут использовать enum-метод __str__(). Замечание: поскольку IntEnum, IntFlag и StrEnum предназначены для замены существующих констант, их метод __str__() был сброшен в их тип данных метода __str__(). [Когда использовать __new__(), а когда __init__()] Метод __new__() должен использоваться всякий раз, когда вы хотите переделать под себя реальное значение элемента Enum. Любые другие модификации могут произойти либо в __new__(), либо в __init__(), предпочтительнее в __init__(). Например, если вы хотите передать несколько элементов в конструктор, но только один из них должен быть значением: >>> class Coordinate(bytes, Enum): Предупреждение: не вызывайте super().__new__(), так как найдется lookup-only __new__; вместо этого используйте тип данных напрямую. [Замечания о тонкостях] Поддерживаемые имена __dunder__. __members__ это упорядоченное read-only отображение элементов member_name:member. Это доступно только на классе. __new__(), если указан, должен создать и возвратить элементы enum; также очень хорошей идеей будет установить _value_ элементов соответствующим образом. После создания всех элементов он больше не используется. Поддерживаемые имена _sunder_. Назначение полей и методов: _name_ – имя элемента. _value_ – значение элемента; может быть установлено в __new__. _missing_() – функция поиска, когда значение не найдено; этот метод может быть переопределен. _ignore_ – список имен либо в виде списка, либо str, которые не будут преобразованы в элементы, и будут удалены их конечного класса. _generate_next_value_() – используется для получения подходящего значения для элемента enum; этот метод может быть переопределен. _add_alias_() – добавит новое имя как псевдоним к существующему элементу. _add_value_alias_() – добавит новое значение как псевдоним существующего элемента. Для примера см. MultiValueEnum. Замечание: для стандартных классов Enum следующее значение выбирается из самого большого видимого значения, инкрементированного на 1. Для классов 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): Замечание: в коде Python 2 атрибут _order_ необходим, поскольку порядок определения теряется, прежде чем его можно будет записать. _Private__names. Приватные имена не преобразуются в элементы enum, однако остаются нормальными атрибутами. Изменено в версии 3.11. Тип элемента Enum. Элементы Enum являются экземплярами своего класса enum, и к ним нормально происходит обращение как к EnumClass.member. В определенных ситуациях, таких как написание пользовательского поведения enum, возможность доступа к одному элементу непосредственно их другого является полезной и поддерживается; однако во избежание конфликтов имен между именами элементов и атрибутами/методами из mixed-in классов, настоятельно рекомендуется использовать имена в верхнем регистре. Изменено в версии 3.5. Создание элементов, смешанных с другими типами данных. Когда выполняется наследование других типов данных, таких как int или str, вместе с Enum, все значения после = передаются конструктору этого типа данных. Например: >>> class MyEnum(IntEnum): # help(int) -> int(x, base=10) -> integer Boolean-значение классов Enum и его элементов. Классы Enum, которые смешиваются с не-Enum типами (такими как int, str, и т. д.) вычисляются в соответствии с правилами смешанных тиров (mixed-in type rules); иначе все элементы вычисляются как True. Чтобы создать ваше собственное вычисление boolean перечисления, зависящее от значения элемента, добавьте в свой класс следующее: def __bool__(self): return bool(self.value) Простые классы Enum всегда вычисляются как True. Классы Enum с методами. Если вы дадите своим enum-подклассам дополнительные методы, наподобие как в показанном далее классе Planet, то эти методы будут отображаться в dir() элемента, но не класса: >>> dir(Planet) ['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS',\ 'VENUS', '__class__', '__doc__', '__members__', '__module__'] Комбинирование элементов Flag. Итерация по комбинации элементов Flag возвратит только те элементы, которые состоят из одного бита: >>> class Color(Flag): Flag и minutia IntFlag. При использовании следующего фрагмента для наших примеров: >>> class Color(IntFlag): .. оказывается верным следующее: • одиночные флаги являются каноническими • флаги нескольких бит и флаги со всеми нулевыми битами являются псевдонимами • итерация возвратит только канонические флаги: >>> list(Color.WHITE) [< Color.RED: 1>, < Color.GREEN: 2>, < Color.BLUE: 4>] • отрицание флага или установка флага возвратит новый флаг / набор флагов с соответствующим положительным значением: >>> Color.BLUE < Color.BLUE: 4> • имена псевдофлагов строятся из имен их членов: >>> (Color.RED | Color.GREEN).name • флаги с несколькими битами, также известные как псевдонимы, могут быть возвращены из операций: >>> Color.RED | Color.BLUE < Color.PURPLE: 5> • проверка членства / включения: флаги с нулевым значением всегда считаются содержащимися: >>> Color.BLACK in Color.WHITE Иначе только если все биты одного флага находятся в другом флаге, будет возвращено True: >>> Color.PURPLE in Color.WHITE Существует новый механизм границ, который управляет, как обрабатываются биты вне диапазона (out-of-range) / недопустимые (invalid): STRICT, CONFORM, EJECT и KEEP: • STRICT –> выбрасывается исключение, когда имеются недопустимые значения. • CONFORM –> отбрасываются любые недопустимые биты. • EJECT –> теряется статус Flag и становится обычным int с имеющимся значением. • KEEP –> сохранение лишних бит: - сохраняется статус Flag и лишние биты. Для Flag по умолчанию используется STRICT, для IntFlag используется умолчание EJECT, и умолчанием для _convert_ будет KEEP (см. ssl.Options [3] для примера, когда необходимо KEEP). [Чем отличаются Enums и Flags] Перечисления имеют пользовательский метакласс, который влияет на многие аспекты как производных классов Enum, так и их экземпляров (членов). Классы Enum. Метакласс EnumType отвечает за предоставление __contains__(), __dir__(), __iter__() и других методов, которые позволяют делать такие вещи с классом Enum, которые терпят неудачу с типовым классом, таким как list(Color) или some_enum_var в Color. EnumType отвечает за то, чтобы различные другие методы в конечном классе Enum были корректными (такие как __new__(), __getnewargs__(), __str__() и __repr__()). Классы Flag. Флаги имеют расширенный вид псевдонимизации: чтобы быть каноничным, значение флага должно быть степенью двойки, и не дублировать имя. Таким образом, в дополнение к определению псевдонима Enum, флаг без значения (также известный как 0) или с более чем одним значением степени двойки (например 3), считается псевдонимом. Элементы Enum (также известные как экземпляры). Самое интересное в элементах enum: они являются синглтонами (singleton). EnumType создает их все, пока он создает сам класс enum, и затем помещает пользовательский __new__() по месту, чтобы гарантировать, что никакие новые экземпляры никогда не будут созданы, возвращая только существующие экземпляры-члены. Из Википедии: "В объектно-ориентированном программировании шаблон синглтона это шаблон разработки ПО, который ограничивает инстанциацию класса единственным экземпляром. Это один из известных шаблонов проектирования 'Банды четырех', который описывает, как решать повторяющиеся проблемы в объектно-ориентированном ПО. Подобный шаблон полезен, когда необходим ровно один объект для координации действий в системе. Если более конкретно, то шаблон синглтона позволяет классам: - гарантировать, что у них имеется только один экземпляр Термин синглтона произошел из соответствующей математической концепции." Элементы Flag. Элементы Flag могут быть возвращены итерацией, так же как класс Flag, и будут возвращены только канонические элементы. Например (обратите внимание, что не показаны BLACK, PURPLE и WHITE): >>> list(Color) [< Color.RED: 1>, < Color.GREEN: 2>, < Color.BLUE: 4>] Инвертирование элемента флага возвратит соответствующее положительное значение вместо отрицательного значения, например: >>> ~Color.RED < Color.GREEN|BLUE: 6> Элементы Flag имеют длину, соответствующую количеству значений степени двойки, которые они содержат. Например: >>> len(Color.PURPLE) [Enum Cookbook] Хотя классы Enum, IntEnum, StrEnum, Flag и IntFlag покрывают большинство случаев использования, они не могут обслужить все возможные варианты применения. Ниже показаны рецепты для некоторых других типов перечислений, которые могут использоваться напрямую или в качестве примеров для создания ваших собственных классов. Пропуск значений. Во многих случаях использования не имеет не важно, какое реальное значение у перечисления. Существует несколько способов определения такого простого типа перечисления: • Использовать экземпляры auto для значения. Использование любого из этих методов означает для пользователя, что эти значения не важны, иа также позволяет добавлять, удалять или переупорядочивать элементы без необходимости перенумерации остальных элементов. Использование auto. Для auto это может выглядеть так: >>> class Color(Enum): Использование object. Для object это может выглядеть так: >>> class Color(Enum): Вот еще хороший пример, почему вы можете захотеть написать свой собственный __repr__(): >>> class Color(Enum): Использование строки описания. Строка в качестве значения может выглядеть так: >>> class Color(Enum): Использование пользовательского __new__(). Автонумерация в __new__() может выглядеть так: >>> class AutoNumber(Enum): Чтобы сделать AutoNumber более общим, добавьте в сигнатуру *args: >>> class AutoNumber(Enum): Тогда если вы наследуете из AutoNumber, то можете написать свой собственный __init__ для обработки любых дополнительных аргументов: >>> class Swatch(AutoNumber): Замечание: метод __new__(), если он определен, используется во время создания элементов Enum; он затем заменяется __new__() для Enum, который используется после создания класса для поиска существующих элементов. Предупреждение: не вызывайте super().__new__(), поскольку будет найден только lookup-only __new__; вместо этого используйте тип данных напрямую, например: obj = int.__new__(cls, value)
OrderedEnum. Упорядоченное перечисление, которое не основано на IntEnum, и поэтому поддерживает нормальные инварианты Enum (такие как не сравниваемые с другими перечислениями): >>> class OrderedEnum(Enum): DuplicateFreeEnum. Вместо создания псевдонима выбросит ошибку, если было найдено дублированное значение элемента: >>> class DuplicateFreeEnum(Enum): Замечание: это полезный пример наследования Enum для добавления или изменения других поведений, а также для запрета псевдонимов. Если единственным желаемым изменением является запрет псеводонимов, то вместо этого можно просто использовать декоратор unique(). MultiValueEnum. Поддерживает возможность более одного значения на элементе: >>> class MultiValueEnum(Enum): Planet. Если определен __new__() или __init__(), то значение перечисления будет передано в эти методы: >>> class Planet(Enum): TimePeriod. Пример отображения используемого атрибута _ignore_: >>> from datetime import timedelta [Подклассы EnumType] Хотя большинство потребностей в перечислении может быть удовлетворено путем настройки подклассов Enum, либо с помощью декораторов, либо с помощью пользовательских функций, класс EnumType может наследоваться для предоставления другого опыта использования Enum. [Ссылки] 1. Python enum HOWTO. |