Is it possible to have an enum of enums in Python? For example, I'd like to have
enumA
enumB
elementA
elementB
enumC
elementC
elementD
And for me to be able to refer to elementA
as enumA.enumB.elementA
, or to refer to elementD
as enumA.enumC.elementD
.
Is this possible? If so, how?
EDIT: When implemented in the naive way:
from enum import Enum
class EnumA(Enum):
class EnumB(Enum):
member = 0
print(EnumA)
print(EnumA.EnumB.member)
It gives:
<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'
If you don't care about inheritance, here's a solution I've used before:
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__}")
It works by overriding the __new__
method of Animal
to find the appropriate sub-enum and return an instance of that.
Usage:
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
However, notably:
isinstance(Animal.Dog, Animal) #=> False
As long as you don't care about that this solution works nicely. Unfortunately there seems to be no way to refer to the outer class inside the definition of an inner class, so there's no easy way to make Dog
extend Animal
.
You can't do this with the enum
stdlib module. If you try it:
class A(Enum):
class B(Enum):
a = 1
b = 2
class C(Enum):
c = 1
d = 2
A.B.a
… you'll just get an exception like:
AttributeError: 'A' object has no attribute 'a'
This is because the enumeration values of A
act like instances of A
, not like instances of their value type. Just like a normal enum holding int
values doesn't have int
methods on the values, the B
won't have Enum
methods. Compare:
class D(Enum):
a = 1
b = 2
D.a.bit_length()
You can, of course, access the underlying value (the int
, or the B
class) explicitly:
D.a.value.bit_length()
A.B.value.a
… but I doubt that's what you want here.
So, could you use the same trick that IntEnum
uses, of subclassing both Enum
and int
so that its enumeration values are int
values, as described in the Others section of the docs?
No, because what type would you subclass? Not Enum
; that's already your type. You can't use type
(the type of arbitrary classes). There's nothing that works.
So, you'd have to use a different Enum implementation with a different design to make this work. Fortunately, there are about 69105 different ones on PyPI and ActiveState to choose from.
For example, when I was looking at building something similar to Swift enumerations (which are closer to ML ADTs than Python/Java/etc. enumerations), someone recommended I look at makeobj
. I forgot to do so, but now I just did, and:
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)
This gives you:
<Object: B -> [a:0, b:1]> <Value: B.b = 1> b 1
It might be nice if it looked at its __qualname__
instead of its __name__
for creating the str/repr values, but otherwise it looks like it does everything you want. And it has some other cool features (not exactly what I was looking for, but interesting…).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With