Skip to content

Commit 679efbb

Browse files
gh-94943: [Enum] improve repr() when inheriting from a dataclass (GH-99740)
Co-authored-by: C.A.M. Gerlach <[email protected]>
1 parent 5da5aa4 commit 679efbb

File tree

5 files changed

+102
-4
lines changed

5 files changed

+102
-4
lines changed

Doc/howto/enum.rst

+25
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,31 @@ sense to allow sharing some common behavior between a group of enumerations.
459459
(See `OrderedEnum`_ for an example.)
460460

461461

462+
.. _enum-dataclass-support:
463+
464+
Dataclass support
465+
-----------------
466+
467+
When inheriting from a :class:`~dataclasses.dataclass`,
468+
the :meth:`~Enum.__repr__` omits the inherited class' name. For example::
469+
470+
>>> @dataclass
471+
... class CreatureDataMixin:
472+
... size: str
473+
... legs: int
474+
... tail: bool = field(repr=False, default=True)
475+
...
476+
>>> class Creature(CreatureDataMixin, Enum):
477+
... BEETLE = 'small', 6
478+
... DOG = 'medium', 4
479+
...
480+
>>> Creature.DOG
481+
<Creature.DOG: size='medium', legs=4>
482+
483+
Use the :func:`!dataclass` argument ``repr=False``
484+
to use the standard :func:`repr`.
485+
486+
462487
Pickling
463488
--------
464489

Doc/library/enum.rst

+2
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,8 @@ Data Types
389389
Using :class:`auto` with :class:`Enum` results in integers of increasing value,
390390
starting with ``1``.
391391

392+
.. versionchanged:: 3.12 Added :ref:`enum-dataclass-support`
393+
392394

393395
.. class:: IntEnum
394396

Lib/enum.py

+17-1
Original file line numberDiff line numberDiff line change
@@ -955,7 +955,15 @@ def _find_data_repr_(mcls, class_name, bases):
955955
return base._value_repr_
956956
elif '__repr__' in base.__dict__:
957957
# this is our data repr
958-
return base.__dict__['__repr__']
958+
# double-check if a dataclass with a default __repr__
959+
if (
960+
'__dataclass_fields__' in base.__dict__
961+
and '__dataclass_params__' in base.__dict__
962+
and base.__dict__['__dataclass_params__'].repr
963+
):
964+
return _dataclass_repr
965+
else:
966+
return base.__dict__['__repr__']
959967
return None
960968

961969
@classmethod
@@ -1551,6 +1559,14 @@ def _power_of_two(value):
15511559
return False
15521560
return value == 2 ** _high_bit(value)
15531561

1562+
def _dataclass_repr(self):
1563+
dcf = self.__dataclass_fields__
1564+
return ', '.join(
1565+
'%s=%r' % (k, getattr(self, k))
1566+
for k in dcf.keys()
1567+
if dcf[k].repr
1568+
)
1569+
15541570
def global_enum_repr(self):
15551571
"""
15561572
use module.enum_name instead of class.enum_name

Lib/test/test_enum.py

+53-3
Original file line numberDiff line numberDiff line change
@@ -2717,17 +2717,67 @@ def upper(self):
27172717

27182718
def test_repr_with_dataclass(self):
27192719
"ensure dataclass-mixin has correct repr()"
2720-
from dataclasses import dataclass
2721-
@dataclass
2720+
#
2721+
# check overridden dataclass __repr__ is used
2722+
#
2723+
from dataclasses import dataclass, field
2724+
@dataclass(repr=False)
27222725
class Foo:
27232726
__qualname__ = 'Foo'
27242727
a: int
2728+
def __repr__(self):
2729+
return 'ha hah!'
27252730
class Entries(Foo, Enum):
27262731
ENTRY1 = 1
27272732
self.assertTrue(isinstance(Entries.ENTRY1, Foo))
27282733
self.assertTrue(Entries._member_type_ is Foo, Entries._member_type_)
27292734
self.assertTrue(Entries.ENTRY1.value == Foo(1), Entries.ENTRY1.value)
2730-
self.assertEqual(repr(Entries.ENTRY1), '<Entries.ENTRY1: Foo(a=1)>')
2735+
self.assertEqual(repr(Entries.ENTRY1), '<Entries.ENTRY1: ha hah!>')
2736+
#
2737+
# check auto-generated dataclass __repr__ is not used
2738+
#
2739+
@dataclass
2740+
class CreatureDataMixin:
2741+
__qualname__ = 'CreatureDataMixin'
2742+
size: str
2743+
legs: int
2744+
tail: bool = field(repr=False, default=True)
2745+
class Creature(CreatureDataMixin, Enum):
2746+
__qualname__ = 'Creature'
2747+
BEETLE = ('small', 6)
2748+
DOG = ('medium', 4)
2749+
self.assertEqual(repr(Creature.DOG), "<Creature.DOG: size='medium', legs=4>")
2750+
#
2751+
# check inherited repr used
2752+
#
2753+
class Huh:
2754+
def __repr__(self):
2755+
return 'inherited'
2756+
@dataclass(repr=False)
2757+
class CreatureDataMixin(Huh):
2758+
__qualname__ = 'CreatureDataMixin'
2759+
size: str
2760+
legs: int
2761+
tail: bool = field(repr=False, default=True)
2762+
class Creature(CreatureDataMixin, Enum):
2763+
__qualname__ = 'Creature'
2764+
BEETLE = ('small', 6)
2765+
DOG = ('medium', 4)
2766+
self.assertEqual(repr(Creature.DOG), "<Creature.DOG: inherited>")
2767+
#
2768+
# check default object.__repr__ used if nothing provided
2769+
#
2770+
@dataclass(repr=False)
2771+
class CreatureDataMixin:
2772+
__qualname__ = 'CreatureDataMixin'
2773+
size: str
2774+
legs: int
2775+
tail: bool = field(repr=False, default=True)
2776+
class Creature(CreatureDataMixin, Enum):
2777+
__qualname__ = 'Creature'
2778+
BEETLE = ('small', 6)
2779+
DOG = ('medium', 4)
2780+
self.assertRegex(repr(Creature.DOG), "<Creature.DOG: .*CreatureDataMixin object at .*>")
27312781

27322782
def test_repr_with_init_data_type_mixin(self):
27332783
# non-data_type is a mixin that doesn't define __new__
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Add :ref:`enum-dataclass-support` to the
2+
:class:`~enum.Enum` :meth:`~enum.Enum.__repr__`.
3+
When inheriting from a :class:`~dataclasses.dataclass`,
4+
only show the field names in the value section of the member :func:`repr`,
5+
and not the dataclass' class name.

0 commit comments

Comments
 (0)