在Python中嵌套类枚举类

时间:2019-07-05 19:38:50

标签: python enums

我正在尝试使用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。现在,我尝试了很多不同的方法,但是该线程已经失控了。

EDIT1

更简单的方法可能是

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

EDIT2

以下是有关如何使用它的几个示例。性能很重要。确实很难准确解释我想要什么。我希望这有道理。

word.find()

2 个答案:

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