bpo-33947: dataclasses no longer can raise RecursionError in repr (GF9916) (#9970)
The reprlib code was copied here instead of importing reprlib. I'm not sure if we really need to avoid the import, but since I expect dataclasses to be more common that reprlib, it seems wise. Plus, the code is small.
(cherry picked from commit dd13c88b53
)
Co-authored-by: Srinivas Thatiparthy (శ్రీనివాస్ తాటిపర్తి) <srinivasreddy@users.noreply.github.com>
This commit is contained in:
parent
bd9c2ce7ac
commit
b9182aa7da
|
@ -5,6 +5,9 @@ import types
|
|||
import inspect
|
||||
import keyword
|
||||
import builtins
|
||||
import functools
|
||||
import _thread
|
||||
|
||||
|
||||
__all__ = ['dataclass',
|
||||
'field',
|
||||
|
@ -337,6 +340,27 @@ 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 mutate locals when exec() is called. Caller
|
||||
|
@ -497,12 +521,13 @@ def _init_fn(fields, frozen, has_post_init, self_name):
|
|||
|
||||
|
||||
def _repr_fn(fields):
|
||||
return _create_fn('__repr__',
|
||||
('self',),
|
||||
['return self.__class__.__qualname__ + f"(' +
|
||||
', '.join([f"{f.name}={{self.{f.name}!r}}"
|
||||
for f in fields]) +
|
||||
')"'])
|
||||
fn = _create_fn('__repr__',
|
||||
('self',),
|
||||
['return self.__class__.__qualname__ + f"(' +
|
||||
', '.join([f"{f.name}={{self.{f.name}!r}}"
|
||||
for f in fields]) +
|
||||
')"'])
|
||||
return _recursive_repr(fn)
|
||||
|
||||
|
||||
def _frozen_get_del_attr(cls, fields):
|
||||
|
|
|
@ -3169,6 +3169,92 @@ class TestReplace(unittest.TestCase):
|
|||
replace(c, x=3)
|
||||
c = replace(c, x=3, y=5)
|
||||
self.assertEqual(c.x, 15)
|
||||
|
||||
def test_recursive_repr(self):
|
||||
@dataclass
|
||||
class C:
|
||||
f: "C"
|
||||
|
||||
c = C(None)
|
||||
c.f = c
|
||||
self.assertEqual(repr(c), "TestReplace.test_recursive_repr.<locals>.C(f=...)")
|
||||
|
||||
def test_recursive_repr_two_attrs(self):
|
||||
@dataclass
|
||||
class C:
|
||||
f: "C"
|
||||
g: "C"
|
||||
|
||||
c = C(None, None)
|
||||
c.f = c
|
||||
c.g = c
|
||||
self.assertEqual(repr(c), "TestReplace.test_recursive_repr_two_attrs"
|
||||
".<locals>.C(f=..., g=...)")
|
||||
|
||||
def test_recursive_repr_indirection(self):
|
||||
@dataclass
|
||||
class C:
|
||||
f: "D"
|
||||
|
||||
@dataclass
|
||||
class D:
|
||||
f: "C"
|
||||
|
||||
c = C(None)
|
||||
d = D(None)
|
||||
c.f = d
|
||||
d.f = c
|
||||
self.assertEqual(repr(c), "TestReplace.test_recursive_repr_indirection"
|
||||
".<locals>.C(f=TestReplace.test_recursive_repr_indirection"
|
||||
".<locals>.D(f=...))")
|
||||
|
||||
def test_recursive_repr_indirection_two(self):
|
||||
@dataclass
|
||||
class C:
|
||||
f: "D"
|
||||
|
||||
@dataclass
|
||||
class D:
|
||||
f: "E"
|
||||
|
||||
@dataclass
|
||||
class E:
|
||||
f: "C"
|
||||
|
||||
c = C(None)
|
||||
d = D(None)
|
||||
e = E(None)
|
||||
c.f = d
|
||||
d.f = e
|
||||
e.f = c
|
||||
self.assertEqual(repr(c), "TestReplace.test_recursive_repr_indirection_two"
|
||||
".<locals>.C(f=TestReplace.test_recursive_repr_indirection_two"
|
||||
".<locals>.D(f=TestReplace.test_recursive_repr_indirection_two"
|
||||
".<locals>.E(f=...)))")
|
||||
|
||||
def test_recursive_repr_two_attrs(self):
|
||||
@dataclass
|
||||
class C:
|
||||
f: "C"
|
||||
g: "C"
|
||||
|
||||
c = C(None, None)
|
||||
c.f = c
|
||||
c.g = c
|
||||
self.assertEqual(repr(c), "TestReplace.test_recursive_repr_two_attrs"
|
||||
".<locals>.C(f=..., g=...)")
|
||||
|
||||
def test_recursive_repr_misc_attrs(self):
|
||||
@dataclass
|
||||
class C:
|
||||
f: "C"
|
||||
g: int
|
||||
|
||||
c = C(None, 1)
|
||||
c.f = c
|
||||
self.assertEqual(repr(c), "TestReplace.test_recursive_repr_misc_attrs"
|
||||
".<locals>.C(f=..., g=1)")
|
||||
|
||||
## def test_initvar(self):
|
||||
## @dataclass
|
||||
## class C:
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
dataclasses now handle recursive reprs without raising RecursionError.
|
Loading…
Reference in New Issue