Python中的嵌套类枚举

2024-09-26 22:10:51 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在尝试用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。现在我已经尝试了很多不同的方法,但是这个思路已经失控了。你知道吗

编辑1

更简单的方法是

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__间接创建的

编辑2

下面是几个如何使用它的例子。表现很重要。但要准确地解释我想要什么真的很难。我希望这有点道理。你知道吗

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

Tags: nameselfgetifexampledeflabelclass
2条回答

^{}去救援!你知道吗

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

可以使用实例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')

相关问题 更多 >

    热门问题