Python 中的枚举枚举?

Enum of enums in Python?

是否可以在 Python 中使用枚举的枚举?例如,我想要

enumA
    enumB
        elementA
        elementB
    enumC
        elementC
        elementD

我可以将 elementA 称为 enumA.enumB.elementA,或将 elementD 称为 enumA.enumC.elementD

这可能吗?如果是,怎么做?

编辑:以天真的方式实施时:

from enum import Enum

class EnumA(Enum):
    class EnumB(Enum):
        member = 0

print(EnumA)
print(EnumA.EnumB.member)

它给出:

<enum 'EnumA'>
Traceback (most recent call last):
  File "Maps.py", line 15, in <module>
    print(EnumA.EnumB.member)
AttributeError: 'EnumA' object has no attribute 'member'

您可以使用 namedtuples 来做这样的事情:

>>> from collections import namedtuple
>>> Foo = namedtuple('Foo', ['bar', 'barz'])
>>> Bar = namedtuple('Bar', ['element_a', 'element_b'])
>>> Barz = namedtuple('Barz', ['element_c', 'element_d'])
>>> bar = Bar('a', 'b')
>>> barz = Barz('c', 'd')
>>> foo = Foo(bar, barz)
>>> foo
Foo(bar=Bar(element_a='a', element_b='b'), barz=Barz(element_c='c', element_d='d'))
>>> foo.bar.element_a
'a'
>>> foo.barz.element_d
'd'

这不是枚举,但也许可以解决您的问题

您不能使用 enum stdlib 模块执行此操作。如果您尝试一下:

class A(Enum):
    class B(Enum):
        a = 1
        b = 2
    class C(Enum):
        c = 1
        d = 2

A.B.a

…你只会得到一个例外:

AttributeError: 'A' object has no attribute 'a'

这是因为 A 的枚举值表现得像 A 的实例,而不是其值类型的实例。就像包含 int 值的普通枚举在值上没有 int 方法一样,B 也不会有 Enum 方法。比较:

class D(Enum):
    a = 1
    b = 2

D.a.bit_length()

当然,您可以显式访问基础值(intB class):

D.a.value.bit_length()
A.B.value.a

…但我怀疑这就是你想要的。


那么,您能否使用 IntEnum 使用的相同技巧,即对 Enumint 进行 subclass 处理,以便其枚举值 int 值,如文档的 Others 部分所述?

不,因为你会选择哪种类型class?不是 Enum;那已经是你喜欢的类型了。您不能使用 type(任意 classes 的类型)。没有任何效果。

因此,您必须使用具有不同设计的不同 Enum 实现才能完成这项工作。幸运的是,PyPI 和 ActiveState 上大约有 69105 个不同的可供选择。


例如,当我正在考虑构建类似于 Swift 枚举的东西(比 Python/Java/etc. 枚举更接近 ML ADT)时,有人建议我看一下 makeobj .我忘记这样做了,但现在我这样做了,并且:

class A(makeobj.Obj):
    class B(makeobj.Obj):
        a, b = makeobj.keys(2)
    class C(makeobj.Obj):
        c, d = makeobj.keys(2)

print(A.B, A.B.b, A.B.b.name, A.B.b.value)

这给你:

<Object: B -> [a:0, b:1]> <Value: B.b = 1> b 1

如果它查看它的 __qualname__ 而不是它的 __name__ 来创建 str/repr 值,这可能会很好,但除此之外它看起来就像你想要的一切。它还有一些其他很酷的功能(不是我想要的,但很有趣……)。

注意 下面很有趣,可能有用,但正如@abarnert 指出的结果 A 枚举没有 Enum 成员 - - 即 list(A) return 是一个空列表。


无需评论 Enum of Enums 是否是个好主意(我还没有决定;),这是可以做到的...而且只需要少量的魔法。

您可以使用 this answer 中的 Constant class:

class Constant:
    def __init__(self, value):
        self.value = value
    def __get__(self, *args):
        return self.value
    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.value)

或者您可以使用新的 aenum 库及其内置的 skip 描述符装饰器(这就是我将展示的内容)。

无论如何,通过将 subEnum classes 包装在描述符中,它们可以避免自己成为成员。

你的例子看起来像:

from aenum import Enum, skip

class enumA(Enum):
    @skip
    class enumB(Enum):
        elementA = 'a'
        elementB = 'b'
    @skip
    class enumC(Enum):
        elementC = 'c'
        elementD = 'd'

然后您可以访问它们:

print(enumA)
print(enumA.enumB)
print(enumA.enumC.elementD)

这给你:

<enum 'enumA'>
<enum 'enumB'>
enumC.elementD

使用 Constantskip 之间的区别是深奥的:在 enumA__dict__ 'enumB' 中 return 一个 Constant 对象(如果使用 Constant)或 <enum 'enumB'> 如果使用 skip;正常访问总是 return <enum 'enumB'>.

在 Python 3.5+ 中,您甚至可以(取消)pickle 嵌套枚举:

print(pickle.loads(pickle.dumps(enumA.enumC.elementD)) is enumA.enumC.elementD)
# True

请注意,subEnum 在其显示中不包含父 Enum;如果这很重要,我会建议增强 EnumMeta 以识别 Constant 描述符并修改其包含的 class' __repr__ -- 但我将把它留作 reader。 ;)

我在基础枚举中创建了一个枚举实现 de __ getattr __ 的枚举

def __getattr__(self, item):
    if item != '_value_':
        return getattr(self.value, item).value
    raise AttributeError

在我的例子中,我有一个枚举的枚举

class enumBase(Enum):
    class innerEnum(Enum):
        class innerInnerEnum(Enum):
           A

enumBase.innerEnum.innerInnerEnum.A

有效

基于属性的解决方案。这也允许实现属性验证器和 attrs 的其他优点:

import enum

import attr


class CoilsTypes(enum.Enum):
    heating: str = "heating"


class FansTypes(enum.Enum):
    plug: str = "plug"


class HrsTypes(enum.Enum):
    plate: str = "plate"
    rotory_wheel: str = "rotory wheel"


class FiltersTypes(enum.Enum):
    bag: str = "bag"
    pleated: str = "pleated"


@attr.dataclass(frozen=True)
class ComponentTypes:
    coils: CoilsTypes = CoilsTypes
    fans: FansTypes = FansTypes
    hrs: HrsTypes = HrsTypes
    filter: FiltersTypes = FiltersTypes


cmp = ComponentTypes()
res = cmp.hrs.plate

如果你不关心继承,这是我以前用过的解决方案:

class Animal:
    class Cat(enum.Enum):
        TIGER = "TIGER"
        CHEETAH = "CHEETAH"
        LION = "LION"

    class Dog(enum.Enum):
        WOLF = "WOLF"
        FOX = "FOX"

    def __new__(cls, name):
        for member in cls.__dict__.values():
            if isinstance(member, enum.EnumMeta) and name in member.__members__:
                return member(name)
        raise ValueError(f"'{name}' is not a valid {cls.__name__}")

它通过覆盖 Animal__new__ 方法来找到合适的子枚举和 return 它的一个实例。

用法:

Animal.Dog.WOLF                    #=> <Dog.WOLF: 'WOLF'>
Animal("WOLF")                     #=> <Dog.WOLF: 'WOLF'>
Animal("WOLF") is Animal.Dog.WOLF  #=> True
Animal("WOLF") is Animal.Dog.FOX   #=> False
Animal("WOLF") in Animal.Dog       #=> True
Animal("WOLF") in Animal.Cat       #=> False
Animal("OWL")                      #=> ValueError: 'OWL' is not a valid Animal

然而,值得注意的是:

isinstance(Animal.Dog, Animal)     #=> False

只要你不关心这个解决方案就可以很好地工作。不幸的是,似乎没有办法在内部 class 的定义中引用外部 class,因此没有简单的方法使 Dog 扩展 Animal.