【问题标题】:Enum of enums in Python?Python中的枚举枚举?
【发布时间】:2015-08-08 21:55:14
【问题描述】:

在 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 语法实际编写此代码。所以……你试过了吗?会发生什么?
  • 我很好奇 - 为什么你首先需要这个功能?这个的具体用例是什么?
  • @inspectorG4dget 我想在我的 RPG 引擎的通用对象系统中提供一个对象分类系统。例如,可以将对象分类为terrain.rocks.smallRock 或武器.melee.swords.shortSword。有没有更好的办法?
  • 这似乎应该是动态信息,而不是静态信息——换句话说,你存储在字符串或元组中的东西,而不是类型的一部分。除非这意味着用于嵌入式 Python 控制台或其他东西?
  • 对象引擎中的对象类型是不可变的——尽管我现在正在重新考虑这一点。

标签: python python-3.x enums


【解决方案1】:

您可以使用 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'

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

【讨论】:

  • 谢谢。虽然这不能解决我的问题,但了解它非常有用。 +1
【解决方案2】:

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 类):

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

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


那么,您能否使用IntEnum 使用的相同技巧,将Enumint 子类化,使其枚举值 int 值,如@中所述987654321@ 文档部分?

不,因为你会继承什么类型?不是Enum;那已经是你的类型了。您不能使用type(任意类的类型)。没有什么行得通的。

因此,您必须使用具有不同设计的不同 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 值可能会很好,但否则它看起来就像你想要的一切。它还有一些其他很酷的功能(不完全是我想要的,但很有趣……)。

【讨论】:

  • 谢谢。这现在更有意义了。你会如何建议我的想法?请参阅我对 OP 的评论。
  • @SilverWingedSeraph:正如我在最后所说的,我将首先查看所有其他枚举实现中的 PyPI 和 ActiveState,看看其中一个是否具有更接近您想要的设计。您所要求的似乎合理,这不是标准库所采用的设计,所以很有可能其他人做到了。
  • 谢谢。我现在决定使用不同的系统,但我会在某个时候回到这个问题并看看那些。
  • 其实可以的。请注意,我并不是说这是一个好主意(还)。如果您好奇,请查看我的答案。
  • @EthanFurman 好吧,您的答案实际上没有 Enum 值,它有 Constant 值。当然,您正在使用Enum 类型来定义 那些Constant 实例,所以看起来您一直在使用枚举。我认为这可能更具误导性而不是帮助,但值得得到答案 - 任何阅读并弄清楚它是如何工作的人都会学到一些有用的东西,如果没有别的。
【解决方案3】:

注意以下内容很有趣,并且可能有用,但正如@abarnert 所指出的那样,生成的A 枚举没有Enum 成员——即list(A) 返回一个空列表.


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

您可以使用this answer 中的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 deriptor 装饰器(这就是我将展示的内容)。

无论如何,通过将 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__'enumB' 将返回一个Constant 对象(如果使用了Constant)或&lt;enum 'enumB'&gt; 如果@使用了 987654340@;正常访问将始终返回&lt;enum 'enumB'&gt;

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

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

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

【讨论】:

    【解决方案4】:

    我像这样在基本枚举中实现了 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
    

    作品

    【讨论】:

    • 这对我来说是最好的答案,作为一个拥有庞大库并依赖枚举的人,很难更改为全新的库。另外return getattr(self.value, item).value 如果改为return getattr(self.value, item) 会很棒!值可通过枚举访问,因此设计保持不变。
    • @kingspp: aenum 是标准库 enum 的替代品。
    • 我知道这是可能的。谢谢。
    • 我不明白为什么,但它有效。你能解释一下机制吗?
    【解决方案5】:

    基于属性的解决方案。这也允许实现属性验证器和 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
    

    【讨论】:

      【解决方案6】:

      如果你不关心继承,这里有一个我之前用过的解决方案:

      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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-03-22
        • 2010-11-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-10
        • 1970-01-01
        相关资源
        最近更新 更多