10000 gh-90104: avoid RecursionError on recursive dataclass field repr (gh-… · python/cpython@0a7936a · GitHub
[go: up one dir, main page]

Skip to content

Commit 0a7936a

Browse files
authored
gh-90104: avoid RecursionError on recursive dataclass field repr (gh-100756)
Avoid RecursionError on recursive dataclass field repr
1 parent cc87487 commit 0a7936a

File tree

3 files changed

+40
-21
lines changed

3 files changed

+40
-21
lines changed

Lib/dataclasses.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,26 @@ def __repr__(self):
223223
# https://bugs.python.org/issue33453 for details.
224224
_MODULE_IDENTIFIER_RE = re.compile(r'^(?:\s*(\w+)\s*\.)?\s*(\w+)')
225225

226+
# This function's logic is copied from "recursive_repr" function in
227+
# reprlib module to avoid dependency.
228+
def _recursive_repr(user_function):
229+
# Decorator to make a repr function return "..." for a recursive
230+
# call.
231+
repr_running = set()
232+
233+
@functools.wraps(user_function)
234+
def wrapper(self):
235+
key = id(self), _thread.get_ident()
236+
if key in repr_running:
237+
return '...'
238+
repr_running.add(key)
239+
try:
240+
result = user_function(self)
241+
finally:
242+
repr_running.discard(key)
243+
return result
244+
return wrapper
245+
226246
class InitVar:
227247
__slots__ = ('type', )
228248

@@ -280,6 +300,7 @@ def __init__(self, default, default_factory, init, repr, hash, compare,
280300
self.kw_only = kw_only
281301
self._field_type = None
282302

303+
@_recursive_repr
283304
def __repr__(self):
284305
return ('Field('
285306
f'name={self.name!r},'
@@ -403,27 +424,6 @@ def _tuple_str(obj_name, fields):
403424
return f'({",".join([f"{obj_name}.{f.name}" for f in fields])},)'
404425

405426

406-
# This function's logic is copied from "recursive_repr" function in
407-
# reprlib module to avoid dependency.
408-
def _recursive_repr(user_function):
409-
# Decorator to make a repr function return "..." for a recursive
410-
# call.
411-
repr_running = set()
412-
413-
@functools.wraps(user_function)
414-
def wrapper(self):
415-
key = id(self), _thread.get_ident()
416-
if key in repr_running:
417-
return '...'
418-
repr_running.add(key)
419-
try:
420-
result = user_function(self)
421-
finally:
422-
repr_running.discard(key)
423-
return result
424-
return wrapper
425-
426-
427427
def _create_fn(name, args, body, *, globals=None, locals=None,
428428
return_type=MISSING):
429429
# Note that we may mutate locals. Callers beware!

Lib/test/test_dataclasses.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,24 @@ def test_field_repr(self):
6868

6969
self.assertEqual(repr_output, expected_output)
7070

71+
def test_field_recursive_repr(self):
72+
rec_field = field()
73+
rec_field.type = rec_field
74+
rec_field.name = "id"
75+
repr_output = repr(rec_field)
76+
77+
self.assertIn(",type=...,", repr_output)
78+
79+
def test_recursive_annotation(self):
80+
class C:
81+
pass
82+
83+
@dataclass
84+
class D:
85+
C: C = field()
86+
87+
self.assertIn(",type=...,", repr(D.__dataclass_fields__["C"]))
88+
7189
def test_dataclass_params_repr(self):
7290
# Even though this is testing an internal implementation detail,
7391
# it's testing a feature we want to make sure is correctly implemented
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Avoid RecursionError on ``repr`` if a dataclass field definition has a cyclic reference.

0 commit comments

Comments
 (0)
0