Skip to content

Commit

Permalink
gh-93464: [Enum] fix auto() failure during multiple assignment (GH-99148
Browse files Browse the repository at this point in the history
)

* fix auto() failure during multiple assignment

i.e. `ONE = auto(), 'text'` will now have `ONE' with the value of `(1,
'text')`.  Before it would have been `(<an auto instance>, 'text')`
  • Loading branch information
ethanfurman committed Nov 6, 2022
1 parent 586b07e commit 8feb7ab
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 11 deletions.
15 changes: 12 additions & 3 deletions Doc/library/enum.rst
Expand Up @@ -242,8 +242,8 @@ Data Types

Member values can be anything: :class:`int`, :class:`str`, etc.. If
the exact value is unimportant you may use :class:`auto` instances and an
appropriate value will be chosen for you. Care must be taken if you mix
:class:`auto` with other values.
appropriate value will be chosen for you. See :class:`auto` for the
details.

.. attribute:: Enum._ignore_

Expand Down Expand Up @@ -778,7 +778,16 @@ Utilities and Decorators
For *Enum* and *IntEnum* that appropriate value will be the last value plus
one; for *Flag* and *IntFlag* it will be the first power-of-two greater
than the last value; for *StrEnum* it will be the lower-cased version of the
member's name.
member's name. Care must be taken if mixing *auto()* with manually specified
values.

*auto* instances are only resolved when at the top level of an assignment:

* ``FIRST = auto()`` will work (auto() is replaced with ``1``);
* ``SECOND = auto(), -2`` will work (auto is replaced with ``2``, so ``2, -2`` is
used to create the ``SECOND`` enum member;
* ``THREE = [auto(), -3]`` will *not* work (``<auto instance>, -3`` is used to
create the ``THREE`` enum member)

``_generate_next_value_`` can be overridden to customize the values used by
*auto*.
Expand Down
33 changes: 25 additions & 8 deletions Lib/enum.py
Expand Up @@ -171,7 +171,8 @@ class auto:
"""
Instances are replaced with an appropriate value in Enum class suites.
"""
value = _auto_null
def __init__(self, value=_auto_null):
self.value = value

def __repr__(self):
return "auto(%r)" % self.value
Expand Down Expand Up @@ -427,15 +428,31 @@ def __setitem__(self, key, value):
elif isinstance(value, member):
# unwrap value here -- it will become a member
value = value.value
non_auto_store = True
single = False
if isinstance(value, auto):
if value.value == _auto_null:
value.value = self._generate_next_value(
key, 1, len(self._member_names), self._last_values[:],
)
self._auto_called = True
value = value.value
single = True
value = (value, )
if isinstance(value, tuple):
auto_valued = []
for v in value:
if isinstance(v, auto):
non_auto_store = False
if v.value == _auto_null:
v.value = self._generate_next_value(
key, 1, len(self._member_names), self._last_values[:],
)
self._auto_called = True
v = v.value
self._last_values.append(v)
auto_valued.append(v)
if single:
value = auto_valued[0]
else:
value = tuple(auto_valued)
self._member_names[key] = None
self._last_values.append(value)
if non_auto_store:
self._last_values.append(value)
super().__setitem__(key, value)

def update(self, members, **more_members):
Expand Down
44 changes: 44 additions & 0 deletions Lib/test/test_enum.py
Expand Up @@ -4116,6 +4116,50 @@ class Dupes(Enum):
third = auto()
self.assertEqual([Dupes.first, Dupes.second, Dupes.third], list(Dupes))

def test_multiple_auto_on_line(self):
class Huh(Enum):
ONE = auto()
TWO = auto(), auto()
THREE = auto(), auto(), auto()
self.assertEqual(Huh.ONE.value, 1)
self.assertEqual(Huh.TWO.value, (2, 3))
self.assertEqual(Huh.THREE.value, (4, 5, 6))
#
class Hah(Enum):
def __new__(cls, value, abbr=None):
member = object.__new__(cls)
member._value_ = value
member.abbr = abbr or value[:3].lower()
return member
def _generate_next_value_(name, start, count, last):
return name
#
MONDAY = auto()
TUESDAY = auto()
WEDNESDAY = auto(), 'WED'
THURSDAY = auto(), 'Thu'
FRIDAY = auto()
self.assertEqual(Hah.MONDAY.value, 'MONDAY')
self.assertEqual(Hah.MONDAY.abbr, 'mon')
self.assertEqual(Hah.TUESDAY.value, 'TUESDAY')
self.assertEqual(Hah.TUESDAY.abbr, 'tue')
self.assertEqual(Hah.WEDNESDAY.value, 'WEDNESDAY')
self.assertEqual(Hah.WEDNESDAY.abbr, 'WED')
self.assertEqual(Hah.THURSDAY.value, 'THURSDAY')
self.assertEqual(Hah.THURSDAY.abbr, 'Thu')
self.assertEqual(Hah.FRIDAY.value, 'FRIDAY')
self.assertEqual(Hah.FRIDAY.abbr, 'fri')
#
class Huh(Enum):
def _generate_next_value_(name, start, count, last):
return count+1
ONE = auto()
TWO = auto(), auto()
THREE = auto(), auto(), auto()
self.assertEqual(Huh.ONE.value, 1)
self.assertEqual(Huh.TWO.value, (2, 2))
self.assertEqual(Huh.THREE.value, (3, 3, 3))

class TestEnumTypeSubclassing(unittest.TestCase):
pass

Expand Down
@@ -0,0 +1 @@
``enum.auto()`` is now correctly activated when combined with other assignment values. E.g. ``ONE = auto(), 'some text'`` will now evaluate as ``(1, 'some text')``.

0 comments on commit 8feb7ab

Please sign in to comment.