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

ethanfurman webhook-mailer at python.org
Tue Dec 6 16:44:03 EST 2022


https://github.com/python/cpython/commit/679efbb080242fc5be63ad873968f05faeef889f
commit: 679efbb080242fc5be63ad873968f05faeef889f
branch: main
author: Ethan Furman <ethan at stoneleaf.us>
committer: ethanfurman <ethan at stoneleaf.us>
date: 2022-12-06T13:43:41-08:00
summary:

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

Co-authored-by: C.A.M. Gerlach <CAM.Gerlach at Gerlach.CAM>

files:
A Misc/NEWS.d/next/Library/2022-11-23-23-58-45.gh-issue-94943.Oog0Zo.rst
M Doc/howto/enum.rst
M Doc/library/enum.rst
M Lib/enum.py
M Lib/test/test_enum.py

diff --git a/Doc/howto/enum.rst b/Doc/howto/enum.rst
index 98d9f4febe2d..3155c6cb977b 100644
--- a/Doc/howto/enum.rst
+++ b/Doc/howto/enum.rst
@@ -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
 --------
 
diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst
index 208aecf11c80..f8875440baf5 100644
--- a/Doc/library/enum.rst
+++ b/Doc/library/enum.rst
@@ -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
 
diff --git a/Lib/enum.py b/Lib/enum.py
index 1b683c702d59..f07b821f1a60 100644
--- a/Lib/enum.py
+++ b/Lib/enum.py
@@ -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
diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py
index b6082cf02b18..146ef39ca384 100644
--- a/Lib/test/test_enum.py
+++ b/Lib/test/test_enum.py
@@ -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__
diff --git a/Misc/NEWS.d/next/Library/2022-11-23-23-58-45.gh-issue-94943.Oog0Zo.rst b/Misc/NEWS.d/next/Library/2022-11-23-23-58-45.gh-issue-94943.Oog0Zo.rst
new file mode 100644
index 000000000000..ed4754e49bd2
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-11-23-23-58-45.gh-issue-94943.Oog0Zo.rst
@@ -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.



More information about the Python-checkins mailing list