Python compiler says I'm adding an extra argument to int in an Enum

Question:

I’m trying to create a custom enumerator that can replace an int, but has additional fields.

from enum import IntEnum

class MD_Fields(IntEnum):
    ACCOUNT = (0, "Account", True)
    M_DESCRIPT = (4, "Description", False)

    def __new__(cls, value: int, description: str, identifier: bool):
        obj = int.__new__(cls, value)
        obj.description = description
        obj.identifier = identifier
        return obj

if __name__ == '__main__':
    print(MD_Fields.M_DESCRIPT)

However, this code raises the following problem:

Traceback (most recent call last):
  File ".../JetBrains/PyCharmCE2022.3/scratches/scratch.py", line 3, in <module>
    class MD_Fields(IntEnum):
  File "/usr/lib/python3.7/enum.py", line 223, in __new__
    enum_member._value_ = member_type(*args)
TypeError: int() takes at most 2 arguments (3 given)

I don’t understand what’s happening.
(I didn’t find a meaningful definition of int.__new__)

Asked By: Fernando César

||

Answers:

You were close – it was only missing the obj._value_ = value assignment, which Enum needs:

from enum import IntEnum

class MD_Fields(IntEnum):
    ACCOUNT = (0, "Account", True)
    M_DESCRIPT = (4, "Description", False)

    def __new__(cls, value: int, description: str, identifier: bool):
        obj = int.__new__(cls, value)
        obj._value_ = value
        obj.description = description
        obj.identifier = identifier
        return obj

After adding that, it works as intended:

>>> for member in MD_Fields:
...     member, int(member), member.description, member.identifier
...
(<MD_Fields.ACCOUNT: 0>, 0, 'Account', True)
(<MD_Fields.M_DESCRIPT: 4>, 4, 'Description', False)

If you were only extending Enum instead of IntEnum (which is just a shortcut for extending both int and Enum), you would use obj = object.__new__(cls) instead.

The reason that _value_ needs to be set is because EnumMeta.__new__ will use its default behavior for setting _value_ when it wasn’t already stored on the member. The relevant portion from the source for EnumMeta.__new__ (in 3.10), which should look familiar from the error that occurred before that attribute was assigned a value in MD_Fields.__new__:

if not hasattr(enum_member, '_value_'):
    if member_type is object:
        enum_member._value_ = value
    else:
        enum_member._value_ = member_type(*args)

Normally, the ._value_ attribute would be populated using the value to the right of the = when the member was defined. That value ends up being exposed through the .value attribute for each member.

In 3.11, the error was improved to include the hint that _value_ needs to be set:

TypeError: _value_ not set in __new__, unable to create it
Answered By: dskrypa
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.