[Python-checkins] bpo-35540 dataclasses.asdict now supports defaultdict fields (gh-32056)
ericvsmith
webhook-mailer at python.org
Thu Oct 6 20:12:08 EDT 2022
https://github.com/python/cpython/commit/c46a423a520b797c3f8c81fef19293864742755d
commit: c46a423a520b797c3f8c81fef19293864742755d
branch: main
author: Tiger <tnie at tuta.io>
committer: ericvsmith <ericvsmith at users.noreply.github.com>
date: 2022-10-06T17:11:59-07:00
summary:
bpo-35540 dataclasses.asdict now supports defaultdict fields (gh-32056)
files:
A Misc/NEWS.d/next/Library/2022-03-22-18-28-55.bpo-35540.nyijX9.rst
M Lib/dataclasses.py
M Lib/test/test_dataclasses.py
diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py
index 65fb8f251861..bf7f290af162 100644
--- a/Lib/dataclasses.py
+++ b/Lib/dataclasses.py
@@ -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))
diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py
index 328dcdcb0bce..637c456dd49e 100644
--- a/Lib/test/test_dataclasses.py
+++ b/Lib/test/test_dataclasses.py
@@ -12,9 +12,9 @@
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 C:
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
index 000000000000..b7aeee6c8c8f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-03-22-18-28-55.bpo-35540.nyijX9.rst
@@ -0,0 +1 @@
+Fix :func:`dataclasses.asdict` crash when :class:`collections.defaultdict` is present in the attributes.
More information about the Python-checkins
mailing list