W. B. Reed
W. B. Reed

Reputation: 1676

How to use enum name as value with other information

I've researched this and found similar answers, but they don't work in this case because auto() is resolved earlier than __new__ is called and only if the whole value is _auto_null.

Basically, what I want is the following:

class MyEnum(str, Enum):
    one = '1 data'
    two = '2 data'

    def __new__(cls, data):
        member = str.__new__(cls, <NEED NAME HERE>)
        member.data = data
        member._value_ = <NEED NAME HERE>

assert MyEnum.one == 'one'
assert MyEnum.one.value == 'one'
assert MyEnum.one.data == '1 data'

However, the name isn't passed into __new__ so there's nothing to fill in <NEED NAME HERE> with.

Then, I tried to use auto() in the following way:

class MyEnumBase(Enum):
    def _generate_next_value_(name, start, count, last_values):
        return name

class MyEnum(str, MyEnumBase):
    one = '1 data'
    two = '2 data'

    def __new__(cls, data):
        member = str.__new__(cls, auto())
        member.data = data
        member._value_ = auto()

assert MyEnum.one == 'one'
assert MyEnum.one.value == 'one'
assert MyEnum.one.data == '1 data'

This doesn't work because auto() is only resolved when used as the sole value when defining a member, as in one = auto(). So it seems it's not even possible to do one = auto(), '1 data' and then take both of those parameters in the __new__ function.

class MyEnum(str, MyEnumBase):
    one = auto(), '1 data'
    two = auto(), '2 data'

    def __new__(cls, name, data):
        member = str.__new__(cls, name)
        member.data = data
        member._value_ = name

It seems my only option is to hardcode the name:

one = 'one', '1 data'

and define the __new__ function to take two params:

def __new__(cls, name, data):
    ...

Am I missing something? Is there a better way to do this? A better way to structure this information?

Upvotes: 5

Views: 570

Answers (1)

Ethan Furman
Ethan Furman

Reputation: 69288

This is definitely advanced behavior, so to use it you'll need the aenum1 library instead.

That code will look like:

from aenum import Enum

class MyEnum(str, Enum, init='value data'):

    def __new__(cls, name, *args, **kwds):
        obj = str.__new__(cls, name)
        obj._value_ = name
        return obj

    def _generate_next_value_(name, start, count, last_values, *args, **kwds):
        return (name, ) + args

    one = '1 data'
    two = '2 data'

The init setting says what the given values should be used for; in this case:

  • the value for each member
  • the data attribute for each member

Also, if fewer items are given than init says there should be, then _generate_next_value_ will be called in an attempt to provide the missing piece(s).

In this case, _generate_next_value adds the name to the given data, which is then passed into __new__.

__new__ uses the name as it's value and ignores the rest

__init__ is automatically generated to handle the non-value items, so it sets the data attribute


For those still using Python 2.7, or need their code to work for 2.7 as well 3.x, the above class should look like this (all changes are in the first block):

class MyEnum(str, Enum):
    _order_ = 'one two'          # only if order actually matters
    _settings_ = AutoValue
    _init_ = 'value data'

    def __new__(cls, name, *args, **kwds):
        obj = str.__new__(cls, name)
        obj._value_ = name
        return obj

    def _generate_next_value_(name, start, count, last_values, *args, **kwds):
        return (name, ) + args

    one = '1 data'
    two = '2 data'

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

Upvotes: 7

Related Questions