]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-35540 dataclasses.asdict now supports defaultdict fields (gh-32056)
authorTiger <tnie@tuta.io>
Fri, 7 Oct 2022 00:11:59 +0000 (19:11 -0500)
committerGitHub <noreply@github.com>
Fri, 7 Oct 2022 00:11:59 +0000 (17:11 -0700)
Lib/dataclasses.py
Lib/test/test_dataclasses.py
Misc/NEWS.d/next/Library/2022-03-22-18-28-55.bpo-35540.nyijX9.rst [new file with mode: 0644]

index 65fb8f251861f367c02fc725f74ba085c0d74425..bf7f290af1622fb27730659152b2ae661c4a23f9 100644 (file)
@@ -1325,6 +1325,14 @@ 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):
         return type(obj)((_asdict_inner(k, dict_factory),
                           _asdict_inner(v, dict_factory))
index 328dcdcb0bce784798357e5fa88b4b3b1be17451..637c456dd49e7a825a93a81324072ba9303ecaef 100644 (file)
@@ -12,9 +12,9 @@ import types
 import weakref
 import unittest
 from unittest.mock import Mock
-from typing import ClassVar, Any, List, Union, Tuple, Dict, Generic, TypeVar, Optional, Protocol
+from typing import ClassVar, Any, List, Union, Tuple, Dict, Generic, TypeVar, Optional, Protocol, DefaultDict
 from typing import get_type_hints
-from collections import deque, OrderedDict, namedtuple
+from collections import deque, OrderedDict, namedtuple, defaultdict
 from functools import total_ordering
 
 import typing       # Needed for the string "typing.ClassVar[int]" to work as an annotation.
@@ -1677,6 +1677,23 @@ class TestCase(unittest.TestCase):
         self.assertIsNot(d['f'], t)
         self.assertEqual(d['f'].my_a(), 6)
 
+    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
+
     def test_helper_astuple(self):
         # Basic tests for astuple(), it should return a new tuple.
         @dataclass
diff --git a/Misc/NEWS.d/next/Library/2022-03-22-18-28-55.bpo-35540.nyijX9.rst b/Misc/NEWS.d/next/Library/2022-03-22-18-28-55.bpo-35540.nyijX9.rst
new file mode 100644 (file)
index 0000000..b7aeee6
--- /dev/null
@@ -0,0 +1 @@
+Fix :func:`dataclasses.asdict` crash when :class:`collections.defaultdict` is present in the attributes.