mirror of https://github.com/python/cpython.git
gh-90104: avoid RecursionError on recursive dataclass field repr (gh-100756)
Avoid RecursionError on recursive dataclass field repr
This commit is contained in:
parent
cc8748712e
commit
0a7936a38f
|
@ -223,6 +223,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', )
|
||||
|
||||
|
@ -280,6 +300,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},'
|
||||
|
@ -403,27 +424,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!
|
||||
|
|
|
@ -68,6 +68,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_dataclass_params_repr(self):
|
||||
# Even though this is testing an internal implementation detail,
|
||||
# it's testing a feature we want to make sure is correctly implemented
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Avoid RecursionError on ``repr`` if a dataclass field definition has a cyclic reference.
|
Loading…
Reference in New Issue