]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-94943: [Enum] improve repr() when inheriting from a dataclass (GH-99740)
authorEthan Furman <ethan@stoneleaf.us>
Tue, 6 Dec 2022 21:43:41 +0000 (13:43 -0800)
committerGitHub <noreply@github.com>
Tue, 6 Dec 2022 21:43:41 +0000 (13:43 -0800)
Co-authored-by: C.A.M. Gerlach <CAM.Gerlach@Gerlach.CAM>
Doc/howto/enum.rst
Doc/library/enum.rst
Lib/enum.py
Lib/test/test_enum.py
Misc/NEWS.d/next/Library/2022-11-23-23-58-45.gh-issue-94943.Oog0Zo.rst [new file with mode: 0644]

index 98d9f4febe2dfaadec81afaa3e2222b13218bdb2..3155c6cb977bf01ee3f7e9794ef15039faca51cf 100644 (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
 --------
 
index 208aecf11c809bc830cd6229a0ae25f6b45edaf2..f8875440baf5e2436ae3441c505c082e6a6cffd5 100644 (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
 
index 1b683c702d59b4afc40d5381425cdb858637000b..f07b821f1a606adf311c1a21096b54784f71ffb7 100644 (file)
@@ -955,7 +955,15 @@ class EnumType(type):
                     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
index b6082cf02b18d70361560ff01100f8763d756660..146ef39ca3846adb44e8bc5a0f82d659e2ec4e85 100644 (file)
@@ -2717,17 +2717,67 @@ class TestSpecial(unittest.TestCase):
 
     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 (file)
index 0000000..ed4754e
--- /dev/null
@@ -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.