diff options
author | Carl Meyer <carl@oddbird.net> | 2023-01-06 00:19:40 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-06 00:19:40 (GMT) |
commit | 0a7936a38f0bab1619ee9fe257880a51c9d839d5 (patch) | |
tree | d80b3a2e48200408d41b415beacd59a1f4b2b0c5 | |
parent | cc8748712e78805c5be4a0a3f98cfb5c35026d0e (diff) | |
download | cpython-0a7936a38f0bab1619ee9fe257880a51c9d839d5.zip cpython-0a7936a38f0bab1619ee9fe257880a51c9d839d5.tar.gz cpython-0a7936a38f0bab1619ee9fe257880a51c9d839d5.tar.bz2 |
gh-90104: avoid RecursionError on recursive dataclass field repr (gh-100756)
Avoid RecursionError on recursive dataclass field repr
-rw-r--r-- | Lib/dataclasses.py | 42 | ||||
-rw-r--r-- | Lib/test/test_dataclasses.py | 18 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2023-01-04-22-10-31.gh-issue-90104.yZk5EX.rst | 1 |
3 files changed, 40 insertions, 21 deletions
diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index b54e169..5c0257e 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -223,6 +223,26 @@ _POST_INIT_NAME = '__post_init__' # 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 @@ class Field: 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! diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index a09f36c..81a36aa 100644 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -68,6 +68,24 @@ class TestCase(unittest.TestCase): 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 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 0000000..6695c92 --- /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. |