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' 56 Answers
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…).
Note The below is interesting, and may be useful, but as @abarnert noted the resulting A Enum doesn't have Enum members -- i.e. list(A) returns an empty list.
Without commenting on whether an Enum of Enums is a good idea (I haven't yet decided ;) , this can be done... and with only a small amount of magic.
You can either use the Constant class from this answer:
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) Or you can use the new aenum library and its built-in skip desriptor decorator (which is what I will show).
At any rate, by wrapping the subEnum classes in a descriptor they are sheltered from becoming members themselves.
Your example then looks like:
from aenum import Enum, skip class enumA(Enum): @skip class enumB(Enum): elementA = 'a' elementB = 'b' @skip class enumC(Enum): elementC = 'c' elementD = 'd' and you can then access them as:
print(enumA) print(enumA.enumB) print(enumA.enumC.elementD) which gives you:
<enum 'enumA'> <enum 'enumB'> enumC.elementD The difference between using Constant and skip is esoteric: in enumA's __dict__ 'enumB' will return a Constant object (if Constant was used) or <enum 'enumB'> if skip was used; normal access will always return <enum 'enumB'>.
In Python 3.5+ you can even (un)pickle the nested Enums:
print(pickle.loads(pickle.dumps(enumA.enumC.elementD)) is enumA.enumC.elementD) # True Do note that the subEnum doesn't include the parent Enum in it's display; if that's important I would suggest enhancing EnumMeta to recognize the Constant descriptor and modify its contained class' __repr__ -- but I'll leave that as an exercise for the reader. ;)
I made an enum of enum implementing de __ getattr __ in the base enum like this
def __getattr__(self, item): if item != '_value_': return getattr(self.value, item).value raise AttributeError In my case I have an enum of enum of enum
class enumBase(Enum): class innerEnum(Enum): class innerInnerEnum(Enum): A And
enumBase.innerEnum.innerInnerEnum.A works
4You can use namedtuples to do something like this:
>>> 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' This is not a enum but, maybe solves your problem
1If 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.
Solution based on attrs. This also allows to implement attributes validators and other goodies of 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