[Python-checkins] [3.10] gh-90104: avoid RecursionError on recursive dataclass field repr (gh-100756) (GH-100785)

miss-islington webhook-mailer at python.org
Thu Jan 5 21:15:33 EST 2023


https://github.com/python/cpython/commit/ebe8d2340733a36fd7555e1c75bb103d9ef465ec
commit: ebe8d2340733a36fd7555e1c75bb103d9ef465ec
branch: 3.10
author: Carl Meyer <carl at oddbird.net>
committer: miss-islington <31488909+miss-islington at users.noreply.github.com>
date: 2023-01-05T18:15:27-08:00
summary:

[3.10] gh-90104: avoid RecursionError on recursive dataclass field repr (gh-100756) (GH-100785)



Avoid RecursionError on recursive dataclass field repr

(cherry picked from commit 0a7936a38f0bab1619ee9fe257880a51c9d839d5)

Automerge-Triggered-By: GH:ericvsmith

files:
A Misc/NEWS.d/next/Library/2023-01-04-22-10-31.gh-issue-90104.yZk5EX.rst
M Lib/dataclasses.py
M Lib/test/test_dataclasses.py

diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py
index e1687a117d6f..9dc67fa47708 100644
--- a/Lib/dataclasses.py
+++ b/Lib/dataclasses.py
@@ -222,6 +222,26 @@ def __repr__(self):
 # https://bugs.python.org/issue33453 for details.
 _MODULE_IDENTIFIER_RE = re.compile(r'^(?:\s*(\w+)\s*\.)?\s*(\w+)')
 
+# This function's logic is copied from "recursive_repr" function in
+# reprlib module to avoid dependency.
+def _recursive_repr(user_function):
+    # Decorator to make a repr function return "..." for a recursive
+    # call.
+    repr_running = set()
+
+    @functools.wraps(user_function)
+    def wrapper(self):
+        key = id(self), _thread.get_ident()
+        if key in repr_running:
+            return '...'
+        repr_running.add(key)
+        try:
+            result = user_function(self)
+        finally:
+            repr_running.discard(key)
+        return result
+    return wrapper
+
 class InitVar:
     __slots__ = ('type', )
 
@@ -279,6 +299,7 @@ def __init__(self, default, default_factory, init, repr, hash, compare,
         self.kw_only = kw_only
         self._field_type = None
 
+    @_recursive_repr
     def __repr__(self):
         return ('Field('
                 f'name={self.name!r},'
@@ -388,27 +409,6 @@ def _tuple_str(obj_name, fields):
     return f'({",".join([f"{obj_name}.{f.name}" for f in fields])},)'
 
 
-# This function's logic is copied from "recursive_repr" function in
-# reprlib module to avoid dependency.
-def _recursive_repr(user_function):
-    # Decorator to make a repr function return "..." for a recursive
-    # call.
-    repr_running = set()
-
-    @functools.wraps(user_function)
-    def wrapper(self):
-        key = id(self), _thread.get_ident()
-        if key in repr_running:
-            return '...'
-        repr_running.add(key)
-        try:
-            result = user_function(self)
-        finally:
-            repr_running.discard(key)
-        return result
-    return wrapper
-
-
 def _create_fn(name, args, body, *, globals=None, locals=None,
                return_type=MISSING):
     # Note that we may mutate locals. Callers beware!
diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py
index e805f0cf4cc7..a642ed96c7ff 100644
--- a/Lib/test/test_dataclasses.py
+++ b/Lib/test/test_dataclasses.py
@@ -67,6 +67,24 @@ def test_field_repr(self):
 
         self.assertEqual(repr_output, expected_output)
 
+    def test_field_recursive_repr(self):
+        rec_field = field()
+        rec_field.type = rec_field
+        rec_field.name = "id"
+        repr_output = repr(rec_field)
+
+        self.assertIn(",type=...,", repr_output)
+
+    def test_recursive_annotation(self):
+        class C:
+            pass
+
+        @dataclass
+        class D:
+            C: C = field()
+
+        self.assertIn(",type=...,", repr(D.__dataclass_fields__["C"]))
+
     def test_named_init_params(self):
         @dataclass
         class C:
diff --git a/Misc/NEWS.d/next/Library/2023-01-04-22-10-31.gh-issue-90104.yZk5EX.rst b/Misc/NEWS.d/next/Library/2023-01-04-22-10-31.gh-issue-90104.yZk5EX.rst
new file mode 100644
index 000000000000..6695c920ee24
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-01-04-22-10-31.gh-issue-90104.yZk5EX.rst
@@ -0,0 +1 @@
+Avoid RecursionError on ``repr`` if a dataclass field definition has a cyclic reference.



More information about the Python-checkins mailing list