Вот рецепты для различных типов перечислений, которые можно использовать напрямую или в качестве примеров для создания собственных.
__new__()
или __init__()
.Enum
без действительных значений.OrderedEnum
._ignore_
.__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
для значения,__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)>]