mirror of
https://github.com/python/cpython.git
synced 2025-07-24 11:44:31 +00:00
[3.11] gh-90104: avoid RecursionError on recursive dataclass field repr (gh-100756) (GH-100784)
Avoid RecursionError on recursive dataclass field repr
(cherry picked from commit 0a7936a38f
)
Automerge-Triggered-By: GH:ericvsmith
This commit is contained in:
parent
d6b8413e94
commit
f488831576
3 changed files with 40 additions and 21 deletions
|
@ -223,6 +223,26 @@ _POST_INIT_NAME = '__post_init__'
|
||||||
# https://bugs.python.org/issue33453 for details.
|
# https://bugs.python.org/issue33453 for details.
|
||||||
_MODULE_IDENTIFIER_RE = re.compile(r'^(?:\s*(\w+)\s*\.)?\s*(\w+)')
|
_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:
|
class InitVar:
|
||||||
__slots__ = ('type', )
|
__slots__ = ('type', )
|
||||||
|
|
||||||
|
@ -280,6 +300,7 @@ class Field:
|
||||||
self.kw_only = kw_only
|
self.kw_only = kw_only
|
||||||
self._field_type = None
|
self._field_type = None
|
||||||
|
|
||||||
|
@_recursive_repr
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return ('Field('
|
return ('Field('
|
||||||
f'name={self.name!r},'
|
f'name={self.name!r},'
|
||||||
|
@ -389,27 +410,6 @@ def _tuple_str(obj_name, fields):
|
||||||
return f'({",".join([f"{obj_name}.{f.name}" for f in 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,
|
def _create_fn(name, args, body, *, globals=None, locals=None,
|
||||||
return_type=MISSING):
|
return_type=MISSING):
|
||||||
# Note that we may mutate locals. Callers beware!
|
# Note that we may mutate locals. Callers beware!
|
||||||
|
|
|
@ -68,6 +68,24 @@ class TestCase(unittest.TestCase):
|
||||||
|
|
||||||
self.assertEqual(repr_output, expected_output)
|
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):
|
def test_named_init_params(self):
|
||||||
@dataclass
|
@dataclass
|
||||||
class C:
|
class C:
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Avoid RecursionError on ``repr`` if a dataclass field definition has a cyclic reference.
|
Loading…
Add table
Add a link
Reference in a new issue