1

I had to create a extra mapping in an Enum to save additional information, like an verbose description of each enum case, but without losing the Enum classes properties. Ex:

class MyEnumBase:
    description = {
        1: 'Description 1',
        2: 'This is anover description',
        3: 'la la la',
    }

class MyEnum(MyEnumBase, Enum):
    First = 1
    Second = 2
    Third = 3

So I access it like this: MyEnum.description[3] == 'la la la'.

How can I extend the Enum class so in the cases where the descriptions are the same as the enum name, it would populate this dictionary with the names of the fields?

Eg:

class MyAnotherEnum(CustomEnumBase):
    aaa = 1
    bbb = 2
    ccc = 3

so that MyAnotherEnum.description[3] == 'ccc', auto generating the description property for every Enum created from this CustomEnumBase.

I tried to extend the Enum class but all the things I've tried failed. I was thinking something like this:

class CustomEnumBase:
    @property
    def names(cls):
        return {
            id:member.name
            for id, member in cls._value2member_map_.items()
        }

There are 2 restrictions:

  1. It should extend python Enum classes, so the places where an Enum is expected it behaves correctly. There are some third party codes that rely on this.

  2. It should keep access to the description as a mapping, as this protocol is already expected and used in the codebase. So changing it to MyAnotherEnum.description()[id] is not viable, as it expect a mapping (dict), not a function.

Allan Deamon
  • 418
  • 6
  • 18
  • 2
    Why would you need to use `description` dict in the first place? `MyAnotherEnum(3).value` would return `ccc`, while `MyAnotherEnum['ccc']` returns 3 – Chen A. Sep 04 '19 at 22:00
  • can explain more what your wanting to achieve with your enum class.. as chen says it doesn't seem like any extension of enum is needed for your use case – Nath Sep 04 '19 at 22:44
  • I have enums that have the id (the number) and the name of the enum. Some times, I need a verbose description of this enums. For this case, there is this way I found for encapsulating it without losing the Enum protocol/api. – Allan Deamon Sep 05 '19 at 15:34
  • The point is there a lot of new cases that the description name is the same as the name, so instead of creating this duplication of code every new Enum, the ideia is to create a programmatically way to make the descriptions to be the names of the enum itself. And when needed, override the description manually when it's different. – Allan Deamon Sep 05 '19 at 15:37

1 Answers1

4

If you are using Python 3.6+ the stdlib Enum will do; otherwise, you'll need to use the aenum1 library. In both cases you'll need a to create your own _generate_next_value_ method:

def _generate_next_value_(name, start, count, last_values):
    return start+count, name
  • name is the name of the member
  • start is the start value for the enumeration (defaults to 1)
  • count is the number of members so far
  • last_values is a list of the member values so far

and borrow classproperty from this answer:

class classproperty:

    def __init__(self, func):
        self._func = func

    def __get__(self, obj, owner):
        return self._func(owner)

If using aenum (my favorite), the base class will look like:

from aenum import Enum, auto

class DescriptionEnum(Enum):
    _init_ = 'value description'

    @staticmethod
    def _generate_next_value_(name, start, count, last_values):
        return start+count, name

    @classproperty
    def description(cls):
        return {
            e.value: e.description
            for e in cls
            }
    print(description)

If using Python 3.6 and the stdlib Enum the base class will look like:

from enum import Enum, auto

class DescriptionEnum(Enum):

    def __new__(cls, value, description):
        obj = object.__new__(cls)
        obj._value_ = value
        obj.description = description
        return obj

    def _generate_next_value_(name, start, count, last_values):
        print('generating value', start+count, name)
        return start+count, name

    @classproperty
    def description(cls):
        return {
            e.value: e.description
            for e in cls
            }

The difference between those two base classes is the _init_ line vs. the __new__ method. In both cases, your final class would be:

class MyAnotherEnum(DescriptionEnum):
    aaa = auto()
    bbb = auto()
    ccc = auto()

and in use:

>>> print(list(MyAnotherEnum))
    [<MyAnotherEnum.aaa: 1>, <MyAnotherEnum.bbb: 2>, <MyAnotherEnum.ccc: 3>]

>>> print(MyAnotherEnum.bbb)
    MyAnotherEnum.bbb

>>> print(MyAnotherEnum.bbb.description)
    bbb

>>> print(MyAnotherEnum.description)
    {1: 'aaa', 2: 'bbb', 3: 'ccc'}

>>> print(MyAnotherEnum.description[2])
    bbb

While your Enums didn't have it, this new one has a description property for each member, as well as for the Enum as a whole. One advantage of this is you can use the same base class for both enums whose name is the description, and enums whose description is specified:

class MyNormalEnum(DescriptionEnum):
    aaa = 1, 'howdy'
    bbb = 2, 'bye'
    ccc = 3, 'whatever'

and in use:

>>> print(list(MyNormalEnum))
    [<MyNormalEnum.aaa: 1>, <MyNormalEnum.bbb: 2>, <MyNormalEnum.ccc: 3>]

>>> print(MyNormalEnum.bbb)
    MyNormalEnum.bbb

>>> print(MyNormalEnum.bbb.description)
    bye

>>> print(MyNormalEnum.description)
    {1: 'howdy', 2: 'bye', 3: 'whatever'}

>>> print(MyNormalEnum.description[2])
    bye

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