From 81d917079ffb35bc835f835b1dcee25b4b9049af Mon Sep 17 00:00:00 2001 From: Tiger Nie Date: Sun, 9 Oct 2022 15:55:13 -0500 Subject: [PATCH 1/5] gh-98169 dataclasses.astuple support DefaultDict --- Lib/dataclasses.py | 24 ++++++++++++++++-------- Lib/test/test_dataclasses.py | 21 +++++++++++++++++---- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index bf7f290af1622f..208785f52f43a9 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1325,15 +1325,15 @@ def _asdict_inner(obj, dict_factory): # generator (which is not true for namedtuples, handled # above). return type(obj)(_asdict_inner(v, dict_factory) for v in obj) - elif isinstance(obj, dict) and hasattr(type(obj), 'default_factory'): - # obj is a defaultdict, which has a different constructor from - # dict as it requires the default_factory as its first arg. - # https://bugs.python.org/issue35540 - result = type(obj)(getattr(obj, 'default_factory')) - for k, v in obj.items(): - result[_asdict_inner(k, dict_factory)] = _asdict_inner(v, dict_factory) - return result elif isinstance(obj, dict): + if hasattr(type(obj), 'default_factory'): + # obj is a defaultdict, which has a different constructor from + # dict as it requires the default_factory as its first arg. + # https://bugs.python.org/issue35540 + result = type(obj)(getattr(obj, 'default_factory')) + for k, v in obj.items(): + result[_asdict_inner(k, dict_factory)] = _asdict_inner(v, dict_factory) + return result return type(obj)((_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory)) for k, v in obj.items()) @@ -1386,6 +1386,14 @@ def _astuple_inner(obj, tuple_factory): # above). return type(obj)(_astuple_inner(v, tuple_factory) for v in obj) elif isinstance(obj, dict): + if hasattr(type(obj), 'default_factory'): + # obj is a defaultdict, which has a different constructor from + # dict as it requires the default_factory as its first arg. + # https://bugs.python.org/issue35540 + result = type(obj)(getattr(obj, 'default_factory')) + for k, v in obj.items(): + result[_astuple_inner(k, tuple_factory)] = _asdict_inner(v, tuple_factory) + return result return type(obj)((_astuple_inner(k, tuple_factory), _astuple_inner(v, tuple_factory)) for k, v in obj.items()) else: diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index 637c456dd49e7a..360f321f917475 100644 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -1680,19 +1680,17 @@ class C: def test_helper_asdict_defaultdict(self): # Ensure asdict() does not throw exceptions when a # defaultdict is a member of a dataclass - @dataclass class C: mp: DefaultDict[str, List] - dd = defaultdict(list) dd["x"].append(12) c = C(mp=dd) d = asdict(c) - assert d == {"mp": {"x": [12]}} - assert d["mp"] is not c.mp # make sure defaultdict is copied + self.assertEqual(d, {"mp": {"x": [12]}}) + self.assertTrue(d["mp"] is not c.mp) # make sure defaultdict is copied def test_helper_astuple(self): # Basic tests for astuple(), it should return a new tuple. @@ -1821,6 +1819,21 @@ class C: t = astuple(c, tuple_factory=list) self.assertEqual(t, ['outer', T(1, ['inner', T(11, 12, 13)], 2)]) + def test_helper_astuple_defaultdict(self): + # Ensure astuple() does not throw exceptions when a + # defaultdict is a member of a dataclass + @dataclass + class C: + mp: DefaultDict[str, List] + + dd = defaultdict(list) + dd["x"].append(12) + c = C(mp=dd) + t = astuple(c) + + self.assertEqual(t, ({"x": [12]},)) + self.assertTrue(t[0] is not dd) # make sure defaultdict is copied + def test_dynamic_class_creation(self): cls_dict = {'__annotations__': {'x': int, 'y': int}, } From 8aa1b3dff4865535d9b4bbefe5a6f6c02425536a Mon Sep 17 00:00:00 2001 From: Tiger Nie Date: Mon, 10 Oct 2022 19:12:21 -0500 Subject: [PATCH 2/5] removed links to the bugs --- Lib/dataclasses.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 208785f52f43a9..275c6bb492183b 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1329,7 +1329,6 @@ def _asdict_inner(obj, dict_factory): if hasattr(type(obj), 'default_factory'): # obj is a defaultdict, which has a different constructor from # dict as it requires the default_factory as its first arg. - # https://bugs.python.org/issue35540 result = type(obj)(getattr(obj, 'default_factory')) for k, v in obj.items(): result[_asdict_inner(k, dict_factory)] = _asdict_inner(v, dict_factory) @@ -1389,7 +1388,6 @@ def _astuple_inner(obj, tuple_factory): if hasattr(type(obj), 'default_factory'): # obj is a defaultdict, which has a different constructor from # dict as it requires the default_factory as its first arg. - # https://bugs.python.org/issue35540 result = type(obj)(getattr(obj, 'default_factory')) for k, v in obj.items(): result[_astuple_inner(k, tuple_factory)] = _asdict_inner(v, tuple_factory) From f39e3d8e2cd8b7b58e16ee9e9e5892c24a82447d Mon Sep 17 00:00:00 2001 From: Tiger Nie Date: Mon, 10 Oct 2022 19:15:12 -0500 Subject: [PATCH 3/5] news.d --- .../next/Library/2022-10-10-19-14-51.gh-issue-98169.DBWIxL.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2022-10-10-19-14-51.gh-issue-98169.DBWIxL.rst diff --git a/Misc/NEWS.d/next/Library/2022-10-10-19-14-51.gh-issue-98169.DBWIxL.rst b/Misc/NEWS.d/next/Library/2022-10-10-19-14-51.gh-issue-98169.DBWIxL.rst new file mode 100644 index 00000000000000..24c3aeecc83f18 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-10-10-19-14-51.gh-issue-98169.DBWIxL.rst @@ -0,0 +1,2 @@ +Fix :func:`dataclasses.astuple` crash when :class:`collections.defaultdict` +is present in the attributes. From fc6a5a9682d7599366ecc8faf4233af02d8de949 Mon Sep 17 00:00:00 2001 From: T Date: Mon, 13 Mar 2023 04:51:04 -0500 Subject: [PATCH 4/5] Update Lib/dataclasses.py cache the output of `type(obj)` to avoid computing it 3 times. Co-authored-by: Pieter Eendebak --- Lib/dataclasses.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 275c6bb492183b..8d7dfb510efa80 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1385,14 +1385,15 @@ def _astuple_inner(obj, tuple_factory): # above). return type(obj)(_astuple_inner(v, tuple_factory) for v in obj) elif isinstance(obj, dict): - if hasattr(type(obj), 'default_factory'): + obj_type = type(obj) + if hasattr(obj_type, 'default_factory'): # obj is a defaultdict, which has a different constructor from # dict as it requires the default_factory as its first arg. - result = type(obj)(getattr(obj, 'default_factory')) + result = obj_type(getattr(obj, 'default_factory')) for k, v in obj.items(): result[_astuple_inner(k, tuple_factory)] = _asdict_inner(v, tuple_factory) return result - return type(obj)((_astuple_inner(k, tuple_factory), _astuple_inner(v, tuple_factory)) + return obj_type((_astuple_inner(k, tuple_factory), _astuple_inner(v, tuple_factory)) for k, v in obj.items()) else: return copy.deepcopy(obj) From 44aaee9b958cc72d8a4954f0475c8dd19657d760 Mon Sep 17 00:00:00 2001 From: T Date: Mon, 13 Mar 2023 07:36:41 -0500 Subject: [PATCH 5/5] Update Lib/dataclasses.py Co-authored-by: Pieter Eendebak --- Lib/dataclasses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 8d7dfb510efa80..e76e68ff1346e4 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1391,7 +1391,7 @@ def _astuple_inner(obj, tuple_factory): # dict as it requires the default_factory as its first arg. result = obj_type(getattr(obj, 'default_factory')) for k, v in obj.items(): - result[_astuple_inner(k, tuple_factory)] = _asdict_inner(v, tuple_factory) + result[_astuple_inner(k, tuple_factory)] = _astuple_inner(v, tuple_factory) return result return obj_type((_astuple_inner(k, tuple_factory), _astuple_inner(v, tuple_factory)) for k, v in obj.items()) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy