Subclassing Python Enum with `auto()`, string values, and an extra property

Question:

I have an Enum subclass that I would like to do two things:

  1. Use the lowercased name of the enum as the value for each item and a property called function. I don’t want to have to type this out each time, so I’m using auto().
  2. Optionally, allow the user to pass a custom function name that overrides the lowercased name.

Here is the code I have come up with to do that:

from enum import Enum, auto


class LowerNameEnum(Enum):
    def _generate_next_value_(name, start, count, last_values):
        print(f"{name=}")
        return name.lower()


class Task(str, LowerNameEnum):
    def __new__(cls, value, function=None):
        obj = str.__new__(cls, value)
        obj._value_ = value

        obj.function = function or "generic_function"

        return obj

    DO_SOMETHING = auto()
    DO_SOMETHING_ELSE = auto()
    DO_ANOTHER_THING = auto(), "do_it_do_it"
    THING_4 = auto()


for t in Task:
    print(t)
    print("  ", t.value)
    print("  ", t.function)

But when I run this, I get the following:

$ python myenum.py
name='DO_SOMETHING'
name='DO_SOMETHING_ELSE'
name='THING_4'
Task.DO_SOMETHING
   do_something
   generic_function
Task.DO_SOMETHING_ELSE
   do_something_else
   generic_function
Task.DO_ANOTHER_THING
   <enum.auto object at 0x103000160>
   do_it_do_it
Task.THING_4
   thing_4
   generic_function

When I try to use the auto() value in combination with the extra argument, I get what looks like a __repr__ of an enum.auto object instead of the lowercased item name. This is because _generate_next_value_ isn’t even called for this one, despite my having passed auto().

What am I doing wrong?


Update: Well, it turns out that value in the __new__ method for the third item is an enum.auto object. So that’s how it’s getting set as the value. I can use isinstance to capture this, which is good. But then my question becomes: how do I find out the name of the item at this point so I can pass it to _generate_next_value_? Or is there some better way to do this?

Asked By: Nick K9

||

Answers:

Until this is fixed (in 3.12), you’ll need to use aenum:

from aenum import Enum

...

class Task(str, LowerNameEnum):
    ...
    DO_ANOTHER_THING = auto(function="do_it_do_it")

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

Answered By: Ethan Furman
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.