6

I would like to create a new Enum (IntEnum) class based on two existing ones. There is a working solution for this, like so:

from enum import unique, IntEnum
from itertools import chain
from collections import OrderedDict

@unique
class FirstEnumClass(IntEnum):
    a = 1
    b = 2

@unique
class SecondEnumClass(IntEnum):
    c = 3
    d = 4

# here a combined class is created:
CombinedEnumClass = unique(IntEnum('CombinedEnumClass', OrderedDict([(i.name, i.value) for i in chain(FirstEnumClass, SecondEnumClass)])))

My question: is there a fancy way to achieve this, so that there is a proper class definition? Like overriding some of the metaclass methods, or so? I would like something like this, so that docstring can also be given:

@unique
class CombinedEnumClass(IntEnum):
    """ docstring """
    # magic needed here

Any idea? Thanks!

jdehesa
  • 58,456
  • 7
  • 77
  • 121
waszil
  • 390
  • 2
  • 5
  • 15
  • You don't need the `OrderedDict`, you can give just the list comprehension. – jdehesa Sep 06 '17 at 11:06
  • I know, it works that way, the thing is that I'm using PyCharm, and it gives a warning if just a list is given: "Expected type 'Integral', got 'List[Tuple[Any, Any]]' instead", and OrderedDict solved that. But true, it works without that. Anyways, I still cannot manage to do this like a class definition. – waszil Sep 06 '17 at 11:09
  • Possible duplicate of [How to extend Python Enum?](https://stackoverflow.com/questions/33679930/how-to-extend-python-enum) – John Crawford Mar 13 '19 at 19:04

2 Answers2

5

Python 2

Using a trick with vars() this can be accomplished:

class CombinedEnum(IntEnum):
    """ doc string """
    cls = vars()
    for member in chain(list(FirstEnumClass), list(SecondEnumClass)):
        cls[member.name] = member.value
    del member, cls

print(list(CombinedEnum))

This works because:

  • vars() returns the current namespace
  • modifying that namespace modifies the class

We delete member and cls because we don't want them to become members.


Python 3

The above doesn't (yet) work with the new Enum in Python3 3.4 and 3.5 (not sure about 3.6). The work-a-round is to use aenum instead, which allows us to tell Enum to ignore certain names:

from aenum import IntEnum

class CombinedEnum(IntEnum):
    """ doc string """
    _ignore_ = 'member cls'
    cls = vars()
    for member in chain(list(FirstEnumClass), list(SecondEnumClass)):
        cls[member.name] = member.value

1 Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
  • It looks good, but it does not work for me: `File "C:\Anaconda3\lib\enum.py", line 66, in __setitem__ raise TypeError('Attempted to reuse key: %r' % key) TypeError: Attempted to reuse key: 'member'` I tried to skip the `cls[member.name] = member.value` line and write a `pass` instead, but still the same is raised. I use Python 3.5.2 with Anaconda3. – waszil Sep 07 '17 at 10:33
  • I tried it in Python 2.7, and it works there. Do you know a way for Python 3.x? – waszil Sep 07 '17 at 11:01
  • 1
    @waszil: Thanks for finding that. I added a Python 3 work around. – Ethan Furman Sep 07 '17 at 18:19
  • @EthanFurman, so why did you [restrict subclassing of enumerations?]( https://docs.python.org/3/library/enum.html#restricted-subclassing-of-enumerations) in the first place. And what does `_ignore_` var is doing? Thanks for your libraries. – Kruupös Sep 08 '17 at 07:51
  • 1
    @OwlMax: subclassing added complications with equality and identity, and we didn't feel it was a net win to allow it; The `_ignore_` variable is telling the `Enum` metaclass to discard those names from the final enum class. – Ethan Furman Sep 08 '17 at 18:45
  • @EthanFurman I have posted a question on enum about "Combine two or more enumeration classes in order to have more levels access". If you have some tips, I am infinitely grateful to you. Thanks in advance https://stackoverflow.com/questions/71527981/combine-two-or-more-enumeration-classes-in-order-to-have-more-levels-access – Gianni Spear Mar 18 '22 at 13:47
0

The library prevents explicitly to do that:

Subclassing an enumeration is allowed only if the enumeration does not define any members.

Allowing subclassing of enums that define members would lead to a violation of some important invariants of types and instances. On the other hand, it makes sense to allow sharing some common behavior between a group of enumerations.

Therefore, I found a Stackoverflow answer using almost the same workaround as you do. I think this is the only way.

Kruupös
  • 5,097
  • 3
  • 27
  • 43
  • Yeah, it seems like this is the only way. meanwhile I tried something like in [this post](https://stackoverflow.com/questions/32214614/automatically-setting-an-enum-members-value-to-its-name), to subclass the EnumMeta metaclass itself and play with its `__new__` method, but eventually all try runs into the same problem: `TypeError: Cannot extend enumerations`. – waszil Sep 06 '17 at 13:53