[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