Skip to content

Commit fb1e950

Browse files
pythongh-91456: [Enum] Deprecate default auto() behavior with mixed value types (pythonGH-91457)
When used with plain Enum, auto() returns the last numeric value assigned, skipping any incompatible member values (such as strings); starting in 3.13 the default auto() for plain Enums will require all the values to be of compatible types, and will return a new value that is 1 higher than any existing value. Co-authored-by: Ethan Furman <ethan@stoneleaf.us>
1 parent 7c439dc commit fb1e950

File tree

4 files changed

+89
-17
lines changed

4 files changed

+89
-17
lines changed

Doc/library/enum.rst

+4
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,10 @@ Utilities and Decorators
761761
``_generate_next_value_`` can be overridden to customize the values used by
762762
*auto*.
763763

764+
.. note:: in 3.13 the default ``"generate_next_value_`` will always return
765+
the highest member value incremented by 1, and will fail if any
766+
member is an incompatible type.
767+
764768
.. decorator:: property
765769

766770
A decorator similar to the built-in *property*, but specifically for

Lib/enum.py

+26-8
Original file line numberDiff line numberDiff line change
@@ -1205,21 +1205,39 @@ def __new__(cls, value):
12051205
def __init__(self, *args, **kwds):
12061206
pass
12071207

1208-
def _generate_next_value_(name, start, count, last_values):
1208+
def _generate_next_value_(name, start, count, last_value):
12091209
"""
12101210
Generate the next value when not given.
12111211
12121212
name: the name of the member
12131213
start: the initial start value or None
12141214
count: the number of existing members
1215-
last_value: the last value assigned or None
1215+
last_value: the list of values assigned
12161216
"""
1217-
for last_value in reversed(last_values):
1218-
try:
1219-
return last_value + 1
1220-
except TypeError:
1221-
pass
1222-
else:
1217+
if not last_value:
1218+
return start
1219+
try:
1220+
last = last_value[-1]
1221+
last_value.sort()
1222+
if last == last_value[-1]:
1223+
# no difference between old and new methods
1224+
return last + 1
1225+
else:
1226+
# trigger old method (with warning)
1227+
raise TypeError
1228+
except TypeError:
1229+
import warnings
1230+
warnings.warn(
1231+
"In 3.13 the default `auto()`/`_generate_next_value_` will require all values to be sortable and support adding +1\n"
1232+
"and the value returned will be the largest value in the enum incremented by 1",
1233+
DeprecationWarning,
1234+
stacklevel=3,
1235+
)
1236+
for v in last_value:
1237+
try:
1238+
return v + 1
1239+
except TypeError:
1240+
pass
12231241
return start
12241242

12251243
@classmethod

Lib/test/test_enum.py

+56-9
Original file line numberDiff line numberDiff line change
@@ -3953,23 +3953,54 @@ class Color(AutoNameEnum):
39533953
self.assertEqual(Color.blue.value, 'blue')
39543954
self.assertEqual(Color.green.value, 'green')
39553955

3956-
def test_auto_garbage(self):
3957-
class Color(Enum):
3958-
red = 'red'
3959-
blue = auto()
3956+
@unittest.skipIf(
3957+
python_version >= (3, 13),
3958+
'mixed types with auto() no longer supported',
3959+
)
3960+
def test_auto_garbage_ok(self):
3961+
with self.assertWarnsRegex(DeprecationWarning, 'will require all values to be sortable'):
3962+
class Color(Enum):
3963+
red = 'red'
3964+
blue = auto()
39603965
self.assertEqual(Color.blue.value, 1)
39613966

3962-
def test_auto_garbage_corrected(self):
3963-
class Color(Enum):
3964-
red = 'red'
3965-
blue = 2
3966-
green = auto()
3967+
@unittest.skipIf(
3968+
python_version >= (3, 13),
3969+
'mixed types with auto() no longer supported',
3970+
)
3971+
def test_auto_garbage_corrected_ok(self):
3972+
with self.assertWarnsRegex(DeprecationWarning, 'will require all values to be sortable'):
3973+
class Color(Enum):
3974+
red = 'red'
3975+
blue = 2
3976+
green = auto()
39673977

39683978
self.assertEqual(list(Color), [Color.red, Color.blue, Color.green])
39693979
self.assertEqual(Color.red.value, 'red')
39703980
self.assertEqual(Color.blue.value, 2)
39713981
self.assertEqual(Color.green.value, 3)
39723982

3983+
@unittest.skipIf(
3984+
python_version < (3, 13),
3985+
'mixed types with auto() will raise in 3.13',
3986+
)
3987+
def test_auto_garbage_fail(self):
3988+
with self.assertRaisesRegex(TypeError, 'will require all values to be sortable'):
3989+
class Color(Enum):
3990+
red = 'red'
3991+
blue = auto()
3992+
3993+
@unittest.skipIf(
3994+
python_version < (3, 13),
3995+
'mixed types with auto() will raise in 3.13',
3996+
)
3997+
def test_auto_garbage_corrected_fail(self):
3998+
with self.assertRaisesRegex(TypeError, 'will require all values to be sortable'):
3999+
class Color(Enum):
4000+
red = 'red'
4001+
blue = 2
4002+
green = auto()
4003+
39734004
def test_auto_order(self):
39744005
with self.assertRaises(TypeError):
39754006
class Color(Enum):
@@ -3991,6 +4022,22 @@ def _generate_next_value_(name, start, count, last):
39914022
self.assertEqual(Color.red.value, 'pathological case')
39924023
self.assertEqual(Color.blue.value, 'blue')
39934024

4025+
@unittest.skipIf(
4026+
python_version < (3, 13),
4027+
'auto() will return highest value + 1 in 3.13',
4028+
)
4029+
def test_auto_with_aliases(self):
4030+
class Color(Enum):
4031+
red = auto()
4032+
blue = auto()
4033+
oxford = blue
4034+
crimson = red
4035+
green = auto()
4036+
self.assertIs(Color.crimson, Color.red)
4037+
self.assertIs(Color.oxford, Color.blue)
4038+
self.assertIsNot(Color.green, Color.red)
4039+
self.assertIsNot(Color.green, Color.blue)
4040+
39944041
def test_duplicate_auto(self):
39954042
class Dupes(Enum):
39964043
first = primero = auto()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Deprecate current default auto() behavior: In 3.13 the default will be for
2+
for auto() to always return the largest member value incremented by
3+
1, and to raise if incompatible value types are used.

0 commit comments

Comments
 (0)