Как я могу получить доступ к атрибутам-членам Enum без .значение (и без использования кортежа позиционных аргументов)?

#python

#python

Вопрос:

При использовании перечислений я предпочитаю не использовать .value везде для доступа к атрибутам элемента.

Итак, я создаю их следующим образом:

 from dataclasses import dataclass
from enum import Enum

@dataclass
class Item:
    foo: int
    bar: int

class Collection(Item, Enum):
    A = 1, 2

# Now I can use Collection.A.foo
  

Однако мой Item класс имеет несколько аргументов, и для удобства чтения (и поддержки IDE) я бы предпочел создавать экземпляры элементов непосредственно в перечислении. Что-то вроде этого (что не работает, поскольку Enum хочет получить кортеж позиционных аргументов, а не экземпляр Item ):

 class Collection(Item, Enum):
    A = Item(1, 2)
  

При этом все еще пользуясь Collection.A.foo доступом к атрибутам (вместо Collection.A.value.foo того, что я получил бы, если бы имел Collection(Enum) ).

Или, по крайней мере, иметь возможность указать ключевые слова, которые будут использоваться для создания экземпляра Item :

 class Collection(Item, Enum):
    A = dict(1, 2)
  

Есть ли способ достичь любого решения? Может быть, с некоторым взломом Item.__new__ ?

Редактировать: я пытаюсь не использовать кортежи в перечислении. (На момент написания этого оба ответа все еще использовали кортежи для определения аргументов элементов.)

Комментарии:

1. Вы рассматривали возможность использования не перечисления, а обычной коллекции (list, set, dict, …) или модуля?

2. Проверка типов перечислений в среде IDE полезна. Но да, мы достаточно повоевали с перечислениями, чтобы ванильные классы как контейнеры (т. Е. не подклассы Enum) выглядели довольно неплохо.

Ответ №1:

Из Enum документации Python — Вы можете попробовать переопределить __init__ or __new__ вашего класса enum и добиться желаемого результата.

Обратите внимание на следующую часть документации о том, когда переопределять какой из них:

Когда использовать __new__() против __init__()

new() необходимо использовать всякий раз, когда вы хотите настроить фактическое значение элемента перечисления. Любые другие изменения могут быть внесены либо в __new__(), либо в __init__(), причем предпочтительнее __init__().

Например, если вы хотите передать конструктору несколько элементов, но хотите, чтобы только один из них был значением (см. __new__ переопределение)

Посмотрите на эти два примера:

Переопределение __new__ :

 >>> 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
  

переопределение __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
  

Используя один из этих методов, вы можете создавать свои собственные, более настраиваемые классы Enum. Даже если вам также нужен ваш Item класс как отдельный класс от enum, вы можете просто переопределить одну из этих двух функций и заставить ее работать (хотя, если Item она была создана только ради перечисления, вам, вероятно, следует объединить ее внутри).

Ответ №2:

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

 from enum import Enum
    
class Collection(Enum):
    A = 1, 2
    B = 3, 4
    C = 5, 6
    D = 7, 8

    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar
  

Пробный запуск

 for item in Collection:
    print(item.foo, item.bar, sep=', ')
  

Результат

 1, 2
3, 4
5, 6
7, 8