How to extend Python Enum?

Calling the Enum class directly and making use of chain allows the extension (joining) of an existing enum.

I came upon the problem of extending enums while working on a CANopen implementation. Parameter indices in the range from 0x1000 to 0x2000 are generic to all CANopen nodes while e.g. the range from 0x6000 onwards depends open whether the node is a drive, io-module, etc.

nodes.py:

from enum import IntEnum

class IndexGeneric(IntEnum):
    """ This enum holds the index value of genric object entrys
    """
    DeviceType    = 0x1000
    ErrorRegister = 0x1001

Idx = IndexGeneric

drives.py:

from itertools import chain
from enum import IntEnum
from nodes import IndexGeneric

class IndexDrives(IntEnum):
    """ This enum holds the index value of drive object entrys
    """
    ControlWord   = 0x6040
    StatusWord    = 0x6041
    OperationMode = 0x6060

Idx= IntEnum('Idx', [(i.name, i.value) for i in chain(IndexGeneric,IndexDrives)])

While uncommon, it is sometimes useful to create an enum from many modules. The aenum1 library supports this with an extend_enum function:

from aenum import Enum, extend_enum

class Index(Enum):
    DeviceType    = 0x1000
    ErrorRegister = 0x1001

for name, value in (
        ('ControlWord', 0x6040),
        ('StatusWord', 0x6041),
        ('OperationMode', 0x6060),
        ):
    extend_enum(Index, name, value)

assert len(Index) == 5
assert list(Index) == [Index.DeviceType, Index.ErrorRegister, Index.ControlWord, Index.StatusWord, Index.OperationMode]
assert Index.DeviceType.value == 0x1000
assert Index.StatusWord.value == 0x6041

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


I tested that way on 3.8. We may inherit existing enum but we need to do it also from base class (at last position).

Docs:

A new Enum class must have one base Enum class, up to one concrete data type, and as many object-based mixin classes as needed. The order of these base classes is:

class EnumName([mix-in, ...,] [data-type,] base-enum):
    pass

Example:

class Cats(Enum):
    SIBERIAN = "siberian"
    SPHINX = "sphinx"


class Animals(Cats, Enum):
    LABRADOR = "labrador"
    CORGI = "corgi"

After that you may access Cats from Animals:

>>> Animals.SIBERIAN
<Cats.SIBERIAN: 'siberian'>

But if you want to iterate over this enum, only new members were accessible:

>>> list(Animals)
[<Animals.LABRADOR: 'labrador'>, <Animals.CORGI: 'corgi'>]

Actually this way is for inheriting methods from base class, but you may use it for members with these restrictions.

Another way (a bit hacky)

As described above, to write some function to join two enums in one. I've wrote that example:

def extend_enum(inherited_enum):
    def wrapper(added_enum):
        joined = {}
        for item in inherited_enum:
            joined[item.name] = item.value
        for item in added_enum:
            joined[item.name] = item.value
        return Enum(added_enum.__name__, joined)
    return wrapper


class Cats(Enum):
    SIBERIAN = "siberian"
    SPHINX = "sphinx"


@extend_enum(Cats)
class Animals(Enum):
    LABRADOR = "labrador"
    CORGI = "corgi"

But here we meet another problems. If we want to compare members it fails:

>>> Animals.SIBERIAN == Cats.SIBERIAN
False

Here we may compare only names and values of newly created members:

>>> Animals.SIBERIAN.value == Cats.SIBERIAN.value
True

But if we need iteration over new Enum, it works ok:

>>> list(Animals)
[<Animals.SIBERIAN: 'siberian'>, <Animals.SPHINX: 'sphinx'>, <Animals.LABRADOR: 'labrador'>, <Animals.CORGI: 'corgi'>]

So choose your way: simple inheritance, inheritance emulation with decorator (recreation in fact), or adding a new dependency like aenum (I haven't tested it, but I expect it support all features I described).


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.

https://docs.python.org/3/library/enum.html#restricted-enum-subclassing

So no, it's not directly possible.