Сообщить об ошибке.

Подробности реализации модуля enum в Python

В материале рассмотрены тонкости реализации модуля enum в Python.

Содержание:


Ограниченный подкласс enum.Enum.

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

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

Кроме того, создание подкласса перечисления разрешено, только если перечисление не определяет никаких членов. Итак, это запрещено:

>>> class MoreColor(Color):
...     PINK = 17
...
# Traceback (most recent call last):
...
# TypeError: Cannot extend enumerations

Но это разрешено:

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

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

Pickling enum.Enum.

Перечисления enum.Enum могут быть pickling и unpickling:

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

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

Примечание. С версией протокола pickle 4 можно легко выделить перечисления, вложенные в другие классы.

Можно изменить способ выделения/извлечения членов enum.Enum путем определения __reduce_ex__() в классе перечисления.

Поддержка __dunder__ имен.

__members__ - это упорядоченное отображение (словарь) элементов member_name:member только для чтения. Доступно только в классе.

__new__(), если он указан, то должен создавать и возвращать члены перечисления. Это также неплохая идея, чтобы установить _value_ соответствующим образом. Как только все члены созданы, он больше не используется.

Поддержка _sunder_ имен.

  • _name_ - имя участника
  • _value_ - значение члена; можно установить/изменить в __new__().
  • _missing_ - функция поиска, используемая, когда значение не найдено. Может быть отменена.
  • _ignore_ - список имен в виде списка или строки, которые не будут преобразованы в члены и будут удалены из последнего класса.
  • _order_ - используется в коде Python 2/3 для обеспечения согласованности порядка членов (атрибут класса, удаляется во время создания класса)
  • generatenextvalue - используется функциональным API и автоматически для получения подходящего значения для члена перечисления; . Может быть отменено.

Новое в Python 3.6: _missing_, _order_, _generate_next_value_.

Новое в Python 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']

Частные имена _Private__names.

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

Изменено в Python 3.11.

Тип члена перечисления.

Члены Enum являются экземплярами своего класса Enum и обычно доступны как EnumClass.member. При определенных обстоятельствах к ним также можно получить доступ как EnumClass.member.member, но вы никогда не должны этого делать! Так как этот поиск может завершиться неудачно или что еще хуже, вернуть что-то, кроме члена Enum, который вы ищете. Это еще одна веская причина использовать all-uppercase для имен членов:

>>> class FieldTypes(Enum):
...     name = 0
...     value = 1
...     size = 2
...
>>> FieldTypes.value.size
# <FieldTypes.size: 2>
>>> FieldTypes.size.value
# 2

Логическое значение классов и членов Enum.

Члены Enum, которые смешиваются с типами, отличными от Enum (такими как int, str и т. д.), оцениваются в соответствии с правилами смешанного типа. В противном случае все члены оцениваются как True. Чтобы сделать логическую оценку собственного Enum зависящей от значения члена, добавьте в класс следующее:

def __bool__(self):
    return bool(self.value)

Классы Enum всегда оцениваются как True.

Классы перечислений с дополнительными методами.

Если предоставить своему подклассу Enum дополнительные методы, такие как класс Planet (в разделе "Интересные примеры классов перечислений"), то эти методы будут отображаться в функции dir() у членов перечисления, но не в самом классе:

>>> dir(Planet)
# ['EARTH', 'JUPITER', 'MARS', 
# 'MERCURY', 'NEPTUNE', 'SATURN', 
# 'URANUS', 'VENUS', '__class__',
# '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH)
# ['__class__', '__doc__', '__module__', 
# 'name', 'surface_gravity', 'value']

Объединение членов класса enum.Flag.

Если комбинация членов Flag не имеет имени, то функция repr() будет включать все именованные флаги и все именованные комбинации флагов, которые находятся в значении:

>>> class Color(Flag):
...     RED = auto()
...     GREEN = auto()
...     BLUE = auto()
...     MAGENTA = RED | BLUE
...     YELLOW = RED | GREEN
...     CYAN = GREEN | BLUE
...
>>> Color(3)  # именованная комбинация
# <Color.YELLOW: 3>
>>> Color(7)      # неименованная комбинация
# <Color.RED|GREEN|BLUE: 7>

Особенности enum.Flag и enum.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 - теряет статус флага и становится обычным целым числом с заданным значением.
  • KEEP - сохраняет лишние биты.
    • сохраняет статус флага и дополнительные биты
    • дополнительные биты не отображаются в итерации
    • дополнительные биты появляются в repr() и str()

Значение по умолчанию для Flag - STRICT, значение по умолчанию для IntFlag - EJECT, а значение по умолчанию для _convert_ - KEEP.