我正在尝试使用Python中的属性制作一些类似于常量/枚举的对象/类。像这样的东西。
from abc import ABC
class Entity(ABC):
# allow *labels as attributes
class Label(ABC):
@property
def spellings(self):
raise NotImplementedError
class PeriodLabel(Label):
@property
def months(self):
raise NotImplementedError
class MONTH(Entity):
class JANUARY(Label):
spellings = ['jan.', 'january']
class FEBRUARY(Label):
spellings = ['febr.', 'february']
.
.
.
class PERIOD(Entity):
class QUARTER(PeriodLabel):
spellings = ['q1', 'q2', 'q3', 'q4']
months = 3
class HALFYEAR(PeriodLabel):
spellings = ['6m']
months = 6
.
.
.
目标是从MONTH
对象到"MONTH"
作为str
。这部分很容易,因为我只能使用MONTH.__name__
。但我也想从"MONTH"
转到MONTH
:
assert Entity("MONTH") == MONTH
我可以通过执行以下操作来实现这一点,但是似乎很客气,我需要快速进行比较,因此我认为有更好的方法。
class Entity(ABC):
def __new__(cls, arg):
try:
print(cls.__name__)
candidate = eval(arg)
if issubclass(candidate, cls):
return candidate
except:
pass
我什至接受assert "MONTH" == MONTH
,但我需要从字符串中获取类。我还需要从"MONTH.JANUARY"
转到MONTH.JANUARY
。现在,我尝试了很多不同的方法,但是该线程已经失控了。
更简单的方法可能是
from typing import List, Optional
class Label:
def __init__(self, spellings: List[str]):
self.spellings = spellings
class Entity:
def __init__(self, **labels: Label):
for k, v in labels.items():
self.__setattr__(k, v)
def get(self, entity: str, label: Optional[str] = None):
raise NotImplementedError # todo: how?
PERIOD = Entity(Q1=Label(['q1', 'q2', 'q3', 'q4']))
assert Entity.get('PERIOD') == PERIOD
assert Entity.get('PERIOD', 'Q1') == PERIOD.Q1
不利之处在于,由于PERIOD.Q1
属性是通过Q1
间接创建的,因此代码__setattr__
的引用是间接创建的,并且代码完成功能无法引用def some_function(entity_name, label_name, spellings)
print(f"{entity_name}-{label_name}:{spellings}"
# example 1
for entity in [MONTH, PERIOD, ...]:
entity_name = entity.__name__
for label in entity:
label_name = entity.__name__
some_function(entity_name, label_name, label.spellings)
# example 2 (given entity_name, label_name as strings)
entity = Entity.get(entity_name)
label = entity.get(label_name)
if entity == PERIODS:
if label.months == 3:
# do something
# example 3 (alternative to example 1)
for label in LABELS: # ALL_LABELS is auto collecting all labels
some_function(label.entity.__name__, label.__name__, label.spellings)
# example 4 (alternative to example 2)
label = LABELS.get(entity_name, label_name)
if label.entity == PERIODS:
if label.months == 3:
# do something
以下是有关如何使用它的几个示例。性能很重要。确实很难准确解释我想要什么。我希望这有道理。
word.find()
答案 0 :(得分:1)
metaclasses
来营救!
from __future__ import annotations
from typing import List, Dict, Tuple, Optional
class EntMeta(type):
_instances = {}
def __new__(mcs, classname: str, base_classes: Tuple[type], attrs: Dict) -> EntMeta:
qualname = attrs.get('__qualname__')
if qualname not in EntMeta._instances:
EntMeta._instances[qualname] = super().__new__(mcs, classname, base_classes, attrs)
return EntMeta._instances[qualname]
def __call__(cls, entity: str, label: Optional[str] = None) -> EntMeta:
if label is None:
qualname = entity
else:
qualname = '.'.join([entity, label])
try:
return cls._instances[qualname]
except KeyError:
raise ValueError(f"{qualname} is not a recognized entity")
class Entity(metaclass=EntMeta):
pass
class Label(metaclass=EntMeta):
@property
def spellings(self) -> List[str]:
raise NotImplementedError
class PeriodLabel(Label):
@property
def months(self) -> int:
raise NotImplementedError
class PERIOD(Entity):
class QUARTER(PeriodLabel):
spellings = ['q1', 'q2', 'q3', 'a4']
months = 3
class HALFYEAR(PeriodLabel):
spellings = ['q1', 'q2', 'q3', 'a4']
months = 6
class MONTH(Entity):
class JANUARY(Label):
spellings = ['jan.', 'january']
assert PERIOD == Entity('PERIOD')
assert MONTH == Entity('MONTH')
assert PERIOD.QUARTER == Entity('PERIOD', 'QUARTER')
assert PERIOD.HALFYEAR == Entity('PERIOD', 'HALFYEAR')
assert MONTH.JANUARY == Entity('MONTH', 'JANUARY')
答案 1 :(得分:1)
一个人可以用实例here所示的属性定义枚举。如果内置枚举可以满足我的需求,我将避免定义自己的元类-这是一个非常粗糙的Poc:
"""Enums Poc"""
import enum
_ALL_LABELS = set() # TODO find a way to encapsulate into Label
class Label(enum.Enum):
def __new__(cls, *args, **kwds):
value = len(cls.__members__) + 1
obj = object.__new__(cls)
obj._value_ = value
return obj
def __init__(self, *spellings):
_ALL_LABELS.add(self)
self.spellings = spellings
class PeriodLabel(Label):
def __init__(self, months, *spellings):
super().__init__(*spellings)
self.months = months
class Entity(enum.Enum):
class MONTH(Label): # better use 'Month' here
JANUARY = ['jan.', 'january']
FEBRUARY = ['febr.', 'february']
...
class PERIOD(PeriodLabel):
QUARTER = 3, ['q1', 'q2', 'q3', 'a4']
HALFYEAR = 6, ['q1', 'q2', 'q3', 'a4']
assert Entity.PERIOD == Entity['PERIOD']
assert Entity.MONTH == Entity['MONTH']
def some_function(entity_name, label_name, spellings):
print(f"{entity_name}-{label_name}:{spellings}")
# example 1
for entity in Entity:
entity_name = entity.name
for label in entity.value: # TODO: directly iterate (not in .value)
label_name = label.name
some_function(entity_name, label_name, label.spellings)
# example 2 (given entity_name, label_name as strings)
entity_name = 'PERIOD'
entity = Entity[entity_name]
label = entity.value['QUARTER']
if entity is Entity.PERIOD:
if label.months == 3:
print('True')