Skip to content

Enum auto() functionally fails following alias creation  #91456

Closed
@oscar-LT

Description

@oscar-LT

What is the issue?

The base _generate_next_value_(name, start, count, last_values) in class Enum is not sufficiently rigorous as a default implementation for assigning values with auto().

Why is it problematic?

For a user employing auto() to assign values for their enums, the expectation is that the function will automatically assign unique values to new enums without the user needing to specify those values manually. However, when used following the creation of an alias to a pre-existing enum, the value generated by auto() is not guaranteed to be unique.

Given the following code:

from enum import Enum, auto

class Example(Enum):
    A = auto()
    B = auto()
    C = A
    D = auto()

the expectation is that A, B, D have been assigned unique values whereas C is an alias of A. A printout of each of the Example enum's values proves that not to be the case though:

>>> print(Example.A)
Example.A
>>> print(Example.B)
Example.B
>>> print(Example.C)
Example.A
>>> print(Example.D)     ### UNEXPECTED!
Example.B

effectively rendering D as an alias to B rather than its own separate value.

Suggested Cause

Upon closer inspection, the reason seems to be that the base _generate_next_value_ which auto() relies on is incrementing the "last value that was assigned" rather than the "last NEW value that was assigned"

cpython/Lib/enum.py

Lines 1171 to 1186 in a8abb76

def _generate_next_value_(name, start, count, last_values):
"""
Generate the next value when not given.
name: the name of the member
start: the initial start value or None
count: the number of existing members
last_value: the last value assigned or None
"""
for last_value in reversed(last_values):
try:
return last_value + 1
except TypeError:
pass
else:
return start

Current Workaround

At the moment, to workaround this issue, the user can:

  • Ensure that all aliases are only declared after any and all enums assigned with auto()
  • Implement a replacement _generate_next_value_(name, start, count, last_values) function as described in the docs

This issue only affects code that combines the use of auto() with aliases. Although straightforward workarounds do exist (overloading _generate_next_value_ is well described in the docs), it seems unintuitive and unfriendly as the default behavior of auto(). Even simply sorting last_values before incrementing may be a sufficient solution to this issue, e.g.

    def _generate_next_value_(name, start, count, last_values):
        # for last_value in reversed(last_values):
        for last_value in sorted(last_values, reverse=True):
            try:
                return last_value + 1
            except TypeError:
                pass
        else:
            return start

Metadata

Metadata

Assignees

Labels

stdlibPython modules in the Lib dirtype-bugAn unexpected behavior, bug, or error

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions