gh-94943: [Enum] improve repr() when inheriting from a dataclass (GH-99740)

Co-authored-by: C.A.M. Gerlach <CAM.Gerlach@Gerlach.CAM>
This commit is contained in:
Ethan Furman 2022-12-06 13:43:41 -08:00 committed by GitHub
parent 5da5aa4c3e
commit 679efbb080
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 102 additions and 4 deletions

View file

@ -459,6 +459,31 @@ sense to allow sharing some common behavior between a group of enumerations.
(See `OrderedEnum`_ for an example.)
.. _enum-dataclass-support:
Dataclass support
-----------------
When inheriting from a :class:`~dataclasses.dataclass`,
the :meth:`~Enum.__repr__` omits the inherited class' name. For example::
>>> @dataclass
... class CreatureDataMixin:
... size: str
... legs: int
... tail: bool = field(repr=False, default=True)
...
>>> class Creature(CreatureDataMixin, Enum):
... BEETLE = 'small', 6
... DOG = 'medium', 4
...
>>> Creature.DOG
<Creature.DOG: size='medium', legs=4>
Use the :func:`!dataclass` argument ``repr=False``
to use the standard :func:`repr`.
Pickling
--------

View file

@ -389,6 +389,8 @@ Data Types
Using :class:`auto` with :class:`Enum` results in integers of increasing value,
starting with ``1``.
.. versionchanged:: 3.12 Added :ref:`enum-dataclass-support`
.. class:: IntEnum

View file

@ -955,7 +955,15 @@ def _find_data_repr_(mcls, class_name, bases):
return base._value_repr_
elif '__repr__' in base.__dict__:
# this is our data repr
return base.__dict__['__repr__']
# double-check if a dataclass with a default __repr__
if (
'__dataclass_fields__' in base.__dict__
and '__dataclass_params__' in base.__dict__
and base.__dict__['__dataclass_params__'].repr
):
return _dataclass_repr
else:
return base.__dict__['__repr__']
return None
@classmethod
@ -1551,6 +1559,14 @@ def _power_of_two(value):
return False
return value == 2 ** _high_bit(value)
def _dataclass_repr(self):
dcf = self.__dataclass_fields__
return ', '.join(
'%s=%r' % (k, getattr(self, k))
for k in dcf.keys()
if dcf[k].repr
)
def global_enum_repr(self):
"""
use module.enum_name instead of class.enum_name

View file

@ -2717,17 +2717,67 @@ def upper(self):
def test_repr_with_dataclass(self):
"ensure dataclass-mixin has correct repr()"
from dataclasses import dataclass
@dataclass
#
# check overridden dataclass __repr__ is used
#
from dataclasses import dataclass, field
@dataclass(repr=False)
class Foo:
__qualname__ = 'Foo'
a: int
def __repr__(self):
return 'ha hah!'
class Entries(Foo, Enum):
ENTRY1 = 1
self.assertTrue(isinstance(Entries.ENTRY1, Foo))
self.assertTrue(Entries._member_type_ is Foo, Entries._member_type_)
self.assertTrue(Entries.ENTRY1.value == Foo(1), Entries.ENTRY1.value)
self.assertEqual(repr(Entries.ENTRY1), '<Entries.ENTRY1: Foo(a=1)>')
self.assertEqual(repr(Entries.ENTRY1), '<Entries.ENTRY1: ha hah!>')
#
# check auto-generated dataclass __repr__ is not used
#
@dataclass
class CreatureDataMixin:
__qualname__ = 'CreatureDataMixin'
size: str
legs: int
tail: bool = field(repr=False, default=True)
class Creature(CreatureDataMixin, Enum):
__qualname__ = 'Creature'
BEETLE = ('small', 6)
DOG = ('medium', 4)
self.assertEqual(repr(Creature.DOG), "<Creature.DOG: size='medium', legs=4>")
#
# check inherited repr used
#
class Huh:
def __repr__(self):
return 'inherited'
@dataclass(repr=False)
class CreatureDataMixin(Huh):
__qualname__ = 'CreatureDataMixin'
size: str
legs: int
tail: bool = field(repr=False, default=True)
class Creature(CreatureDataMixin, Enum):
__qualname__ = 'Creature'
BEETLE = ('small', 6)
DOG = ('medium', 4)
self.assertEqual(repr(Creature.DOG), "<Creature.DOG: inherited>")
#
# check default object.__repr__ used if nothing provided
#
@dataclass(repr=False)
class CreatureDataMixin:
__qualname__ = 'CreatureDataMixin'
size: str
legs: int
tail: bool = field(repr=False, default=True)
class Creature(CreatureDataMixin, Enum):
__qualname__ = 'Creature'
BEETLE = ('small', 6)
DOG = ('medium', 4)
self.assertRegex(repr(Creature.DOG), "<Creature.DOG: .*CreatureDataMixin object at .*>")
def test_repr_with_init_data_type_mixin(self):
# non-data_type is a mixin that doesn't define __new__

View file

@ -0,0 +1,5 @@
Add :ref:`enum-dataclass-support` to the
:class:`~enum.Enum` :meth:`~enum.Enum.__repr__`.
When inheriting from a :class:`~dataclasses.dataclass`,
only show the field names in the value section of the member :func:`repr`,
and not the dataclass' class name.