Skip to content

Commit c961d14

Browse files
authored
gh-94601: [Enum] fix inheritance for __str__ and friends (GH-94942)
1 parent 07aeb74 commit c961d14

File tree

2 files changed

+42
-10
lines changed

2 files changed

+42
-10
lines changed

Lib/enum.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,10 @@ def __set_name__(self, enum_class, member_name):
247247
if not enum_class._use_args_:
248248
enum_member = enum_class._new_member_(enum_class)
249249
if not hasattr(enum_member, '_value_'):
250-
enum_member._value_ = value
250+
try:
251+
enum_member._value_ = enum_class._member_type_(*args)
252+
except Exception as exc:
253+
enum_member._value_ = value
251254
else:
252255
enum_member = enum_class._new_member_(enum_class, *args)
253256
if not hasattr(enum_member, '_value_'):
@@ -562,7 +565,13 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k
562565
classdict['__str__'] = enum_class.__str__
563566
for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'):
564567
if name not in classdict:
565-
setattr(enum_class, name, getattr(first_enum, name))
568+
# check for mixin overrides before replacing
569+
enum_method = getattr(first_enum, name)
570+
found_method = getattr(enum_class, name)
571+
object_method = getattr(object, name)
572+
data_type_method = getattr(member_type, name)
573+
if found_method in (data_type_method, object_method):
574+
setattr(enum_class, name, enum_method)
566575
#
567576
# for Flag, add __or__, __and__, __xor__, and __invert__
568577
if Flag is not None and issubclass(enum_class, Flag):
@@ -937,16 +946,18 @@ def _find_data_repr_(mcls, class_name, bases):
937946
@classmethod
938947
def _find_data_type_(mcls, class_name, bases):
939948
data_types = set()
949+
base_chain = set()
940950
for chain in bases:
941951
candidate = None
942952
for base in chain.__mro__:
953+
base_chain.add(base)
943954
if base is object:
944955
continue
945956
elif issubclass(base, Enum):
946957
if base._member_type_ is not object:
947958
data_types.add(base._member_type_)
948959
break
949-
elif '__new__' in base.__dict__:
960+
elif '__new__' in base.__dict__ or '__init__' in base.__dict__:
950961
if issubclass(base, Enum):
951962
continue
952963
data_types.add(candidate or base)
@@ -1658,7 +1669,13 @@ def convert_class(cls):
16581669
enum_class = type(cls_name, (etype, ), body, boundary=boundary, _simple=True)
16591670
for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'):
16601671
if name not in body:
1661-
setattr(enum_class, name, getattr(etype, name))
1672+
# check for mixin overrides before replacing
1673+
enum_method = getattr(etype, name)
1674+
found_method = getattr(enum_class, name)
1675+
object_method = getattr(object, name)
1676+
data_type_method = getattr(member_type, name)
1677+
if found_method in (data_type_method, object_method):
1678+
setattr(enum_class, name, enum_method)
16621679
gnv_last_values = []
16631680
if issubclass(enum_class, Flag):
16641681
# Flag / IntFlag
@@ -1989,7 +2006,6 @@ def _old_convert_(etype, name, module, filter, source=None, *, boundary=None):
19892006
members.sort(key=lambda t: t[0])
19902007
cls = etype(name, members, module=module, boundary=boundary or KEEP)
19912008
cls.__reduce_ex__ = _reduce_ex_by_global_name
1992-
cls.__repr__ = global_enum_repr
19932009
return cls
19942010

19952011
_stdlib_enums = IntEnum, StrEnum, IntFlag

Lib/test/test_enum.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2693,23 +2693,39 @@ def test_repr_with_dataclass(self):
26932693
@dataclass
26942694
class Foo:
26952695
__qualname__ = 'Foo'
2696-
a: int = 0
2696+
a: int
26972697
class Entries(Foo, Enum):
2698-
ENTRY1 = Foo(1)
2698+
ENTRY1 = 1
2699+
self.assertTrue(isinstance(Entries.ENTRY1, Foo))
2700+
self.assertTrue(Entries._member_type_ is Foo, Entries._member_type_)
2701+
self.assertTrue(Entries.ENTRY1.value == Foo(1), Entries.ENTRY1.value)
26992702
self.assertEqual(repr(Entries.ENTRY1), '<Entries.ENTRY1: Foo(a=1)>')
27002703

2701-
def test_repr_with_non_data_type_mixin(self):
2704+
def test_repr_with_init_data_type_mixin(self):
27022705
# non-data_type is a mixin that doesn't define __new__
27032706
class Foo:
27042707
def __init__(self, a):
27052708
self.a = a
27062709
def __repr__(self):
27072710
return f'Foo(a={self.a!r})'
27082711
class Entries(Foo, Enum):
2709-
ENTRY1 = Foo(1)
2710-
2712+
ENTRY1 = 1
2713+
#
27112714
self.assertEqual(repr(Entries.ENTRY1), '<Entries.ENTRY1: Foo(a=1)>')
27122715

2716+
def test_repr_and_str_with_non_data_type_mixin(self):
2717+
# non-data_type is a mixin that doesn't define __new__
2718+
class Foo:
2719+
def __repr__(self):
2720+
return 'Foo'
2721+
def __str__(self):
2722+
return 'ooF'
2723+
class Entries(Foo, Enum):
2724+
ENTRY1 = 1
2725+
#
2726+
self.assertEqual(repr(Entries.ENTRY1), 'Foo')
2727+
self.assertEqual(str(Entries.ENTRY1), 'ooF')
2728+
27132729
def test_value_backup_assign(self):
27142730
# check that enum will add missing values when custom __new__ does not
27152731
class Some(Enum):

0 commit comments

Comments
 (0)