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

Примеры использования модуля enum в Python

Рецепты для различных типов перечислений

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


Использование __new__() или __init__().

Если определены __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       # в килограммах
...         self.radius = radius   # в метрах
...     @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

Когда использовать __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

Перечисления без действительных значений.

Во многих случаях использования все равно, каково действительное значение перечисления. Есть несколько способов определить этот тип простого перечисления:

  • использовать экземпляры enum.auto для значения,
  • использовать экземпляры объекта object в качестве значения,
  • используйте описательную строку в качестве значения,
  • использовать кортеж в качестве значения и пользовательский __new__() для замены кортежа на значение типа int.

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

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

>>> class NoValue(Enum):
...     def __repr__(self):
...         return '<%s.%s>' % (self.__class__.__name__, self.name)

Использование enum.auto.

Использование enum.auto будет выглядеть так:

>>> class Color(NoValue):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.GREEN
# <Color.GREEN>

Использование object.

Использование object будет выглядеть так:

>>> class Color(NoValue):
...     RED = object()
...     GREEN = object()
...     BLUE = object()
...
>>> Color.GREEN
# <Color.GREEN>

Это также хороший пример того, почему иногда нужно написать свой собственный __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(NoValue):
...     RED = 'stop'
...     GREEN = 'go'
...     BLUE = 'too fast!'
...
>>> Color.GREEN
# <Color.GREEN>
>>> Color.GREEN.value
# 'go'

Использование пользовательского __new__().

Использование пользовательского __new__() будет выглядеть так:

>>> class AutoNumber(NoValue):
...     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>
>>> Color.GREEN.value
# 2

Чтобы сделать AutoNumber более универсальным, добавьте *args:

>>> class AutoNumber(NoValue):
...     def __new__(cls, *args): # это единственное изменение
...         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 = () # Новый цвет, кода Pantone пока нет!
...
>>> Swatch.SEA_GREEN
# <Swatch.SEA_GREEN: 2>
>>> Swatch.SEA_GREEN.pantone
# '1246'
>>> Swatch.BLEACHED_CORAL.pantone
# 'unknown'

Примечание. Метод __new__(), если он определен, используется во время создания членов Enum, затем он заменяется __new__() Enum, который используется после создания класса для поиска существующих членов.


Упорядоченное перечисление OrderedEnum.

Упорядоченное перечисление, которое не основано на enum.IntEnum и поэтому поддерживает обычные инварианты enum.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 для добавления или изменения другого поведения, а также для запрета псевдонимов. Если единственное желаемое изменение - это запрет псевдонимов, то вместо этого можно использовать декоратор @enum.unique().


Пример использования атрибута _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)>]