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:
parent
5da5aa4c3e
commit
679efbb080
@ -459,6 +459,31 @@ sense to allow sharing some common behavior between a group of enumerations.
|
|||||||
(See `OrderedEnum`_ for an example.)
|
(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
|
Pickling
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
@ -389,6 +389,8 @@ Data Types
|
|||||||
Using :class:`auto` with :class:`Enum` results in integers of increasing value,
|
Using :class:`auto` with :class:`Enum` results in integers of increasing value,
|
||||||
starting with ``1``.
|
starting with ``1``.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.12 Added :ref:`enum-dataclass-support`
|
||||||
|
|
||||||
|
|
||||||
.. class:: IntEnum
|
.. class:: IntEnum
|
||||||
|
|
||||||
|
16
Lib/enum.py
16
Lib/enum.py
@ -955,6 +955,14 @@ class EnumType(type):
|
|||||||
return base._value_repr_
|
return base._value_repr_
|
||||||
elif '__repr__' in base.__dict__:
|
elif '__repr__' in base.__dict__:
|
||||||
# this is our data repr
|
# this is our data 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 base.__dict__['__repr__']
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -1551,6 +1559,14 @@ def _power_of_two(value):
|
|||||||
return False
|
return False
|
||||||
return value == 2 ** _high_bit(value)
|
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):
|
def global_enum_repr(self):
|
||||||
"""
|
"""
|
||||||
use module.enum_name instead of class.enum_name
|
use module.enum_name instead of class.enum_name
|
||||||
|
@ -2717,17 +2717,67 @@ class TestSpecial(unittest.TestCase):
|
|||||||
|
|
||||||
def test_repr_with_dataclass(self):
|
def test_repr_with_dataclass(self):
|
||||||
"ensure dataclass-mixin has correct repr()"
|
"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:
|
class Foo:
|
||||||
__qualname__ = 'Foo'
|
__qualname__ = 'Foo'
|
||||||
a: int
|
a: int
|
||||||
|
def __repr__(self):
|
||||||
|
return 'ha hah!'
|
||||||
class Entries(Foo, Enum):
|
class Entries(Foo, Enum):
|
||||||
ENTRY1 = 1
|
ENTRY1 = 1
|
||||||
self.assertTrue(isinstance(Entries.ENTRY1, Foo))
|
self.assertTrue(isinstance(Entries.ENTRY1, Foo))
|
||||||
self.assertTrue(Entries._member_type_ is Foo, Entries._member_type_)
|
self.assertTrue(Entries._member_type_ is Foo, Entries._member_type_)
|
||||||
self.assertTrue(Entries.ENTRY1.value == Foo(1), Entries.ENTRY1.value)
|
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):
|
def test_repr_with_init_data_type_mixin(self):
|
||||||
# non-data_type is a mixin that doesn't define __new__
|
# non-data_type is a mixin that doesn't define __new__
|
||||||
|
@ -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.
|
Loading…
x
Reference in New Issue
Block a user