Enum of enums in Python?
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. ;)
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…).