Python中的枚举中的枚举?

15

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

首先,您需要使用stdlib enum模块或第三方模块以Python语法编写此代码。那么,您尝试过了吗?会发生什么? - abarnert
我很好奇 - 您为什么需要此功能?这是什么特定用例? - inspectorG4dget
1
@inspectorG4dget 我想为我的RPG引擎的通用对象系统提供一个分类对象的系统。例如,一个对象可以被分类为terrain.rocks.smallRock或者weapons.melee.swords.shortSword。有更好的方法吗? - Leonora Tindall
1
这似乎应该是动态信息,而不是静态信息——换句话说,应该将其存储在字符串或元组中,而不是作为类型的一部分。除非这是用于嵌入式Python控制台之类的东西? - abarnert
对象类型在对象引擎中是不可变的 - 尽管我现在正在重新考虑这一点。 - Leonora Tindall
7个回答

17

你不能使用 enum 标准库模块完成这个操作。如果你尝试:

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类):
D.a.value.bit_length()
A.B.value.a

...但我怀疑这不是你想要的。


那么,你能使用与 IntEnum 相同的技巧,同时继承 Enumint 来使其枚举值成为 int 值,正如文档中 Others 部分所描述的那样吗?

不行,因为你要继承什么类型呢?不能是 Enum ;它已经是你的类型。你也不能使用 type (任意类的类型)。没有任何东西可以工作。

所以,你必须使用一个具有不同设计的不同 Enum 实现来使其工作。幸运的是,在 PyPI 和 ActiveState 上有大约 69105 种不同的选择。


例如,当我在考虑构建类似于 Swift 枚举(更接近于 ML ADT 而不是 Python/Java 等枚举)的东西时,有人建议我看看 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值会更好,但除此之外,它似乎能够满足你的所有需求。而且它还有一些其他很酷的功能(虽然不完全是我在寻找的,但还是很有趣...)。


谢谢。现在这样就清楚多了。你认为我应该怎么做才能实现我的想法?请看我对原帖的评论。 - Leonora Tindall
@SilverWingedSeraph: 正如我在结尾所说的那样,我首先会查看PyPI和ActiveState上的所有其他枚举实现,看看它们中是否有一个更接近您想要的设计。您要求的似乎是“合理的”,只是不是stdlib采用的设计,因此很有可能其他人已经这样做了。 - abarnert
谢谢。我已经决定暂时选择另一个系统,但我会在某个时候回来查看这些问题。 - Leonora Tindall
@EthanFurman 确定property返回的值是一个property实例。如果A.prop是一个返回字符串的属性,则A().prop的类型为str,但A.prop的类型为property。(两者都不是@property参数的相同类型,即在self和type上返回字符串的函数。)像你的非数据描述符一样,事情有点不同,但描述符仍然是一个独立的东西。(如果不是这样,为什么要为它定义一个__repr__?) - abarnert
问题解决了:aenum包含一个skip装饰器,可以将项目保护不成为成员,元类会提取原始值并将其放置在类的__dict__中。(有关详细信息,请参见我的更新。) - Ethan Furman
显示剩余4条评论

10

注�:以下内容很有趣,�能会很有用,但正如@abarnert所指出的那样,生�的A�举�包��举�员-�list(A)返�一个空列表。


�评论是�将�举嵌套在�举中是个好主�(我还没决定😉),这是�以��的...而且�需�一点点魔法。

你�以使用此答案中的Constant类:

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类包装在描述符中,它们都可以避免成为成员本身。
然后您的示例看起来像这样:
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__中,如果使用Constant'enumB'将返回一个Constant对象;如果使用skip,则返回<enum 'enumB'>。但是无论哪种方式,通过普通访问始终会返回<enum 'enumB'>
在Python 3.5+中,甚至可以对嵌套的枚举类型进行(反)序列化操作:
print(pickle.loads(pickle.dumps(enumA.enumC.elementD)) is enumA.enumC.elementD)
# True

请注意,subEnum中不包括其父级Enum的显示;如果这很重要,建议增强EnumMeta以识别Constant描述符并修改其包含类的__repr__ - 但我将把它留给读者练习。 ;)

7

我创建了一个枚举的枚举,并像这样在基础枚举中实现了__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

And

enumBase.innerEnum.innerInnerEnum.A

工作


3
对我来说,这是最好的答案,因为我有一个庞大的图书馆并且依赖于枚举,所以很难改变到一个全新的库。另外如果return getattr(self.value, item).value 改成 return getattr(self.value, item) 就更好了!由于值可以通过枚举访问,因此设计保持不变。 - kingspp
1
@aenum:aenum是标准库enum的即插即用替代品。 - Ethan Furman
我知道这是可能的。谢谢。 - DonkeyKong
我不知道为什么,但它可以工作。你能解释一下原理吗? - ggguser

2
如果您不关心继承,这是我以前使用过的解决方案:
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__方法来查找适当的子枚举并返回该实例来实现。

使用方法:

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

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

1
您可以使用命名元组来完成类似以下的操作:
>>> 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'

这不是枚举,但可能可以解决您的问题。

谢谢。虽然这并没有解决我的问题,但知道这个信息非常有用。+1 - Leonora Tindall

0

试试这个:

# python3.7
import enum


class A(enum.Enum):
    def __get__(self, instance, owner):
        return self.value
    
    class B(enum.IntEnum):
        a = 1
        b = 2
    
    class C(enum.IntEnum):
        c = 1
        d = 2
    
    # this is optional (it just adds 'A.' before the B and C enum names)
    B.__name__ = B.__qualname__
    C.__name__ = C.__qualname__


print(A.C.d)        # prints: A.C.d
print(A.B.b.value)  # prints: 2


0

基于 attrs 的解决方案。这也允许实现 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接