Skip to content

Commit 71e37d9

Browse files
kwspeendebakpt
andauthored
gh-98169 dataclasses.astuple support DefaultDict (#98170)
Co-authored-by: Pieter Eendebak <pieter.eendebak@gmail.com>
1 parent 85ba8a3 commit 71e37d9

File tree

3 files changed

+35
-13
lines changed

3 files changed

+35
-13
lines changed

Lib/dataclasses.py

+16-9
Original file line numberDiff line numberDiff line change
@@ -1321,15 +1321,14 @@ def _asdict_inner(obj, dict_factory):
13211321
# generator (which is not true for namedtuples, handled
13221322
# above).
13231323
return type(obj)(_asdict_inner(v, dict_factory) for v in obj)
1324-
elif isinstance(obj, dict) and hasattr(type(obj), 'default_factory'):
1325-
# obj is a defaultdict, which has a different constructor from
1326-
# dict as it requires the default_factory as its first arg.
1327-
# https://bugs.python.org/issue35540
1328-
result = type(obj)(getattr(obj, 'default_factory'))
1329-
for k, v in obj.items():
1330-
result[_asdict_inner(k, dict_factory)] = _asdict_inner(v, dict_factory)
1331-
return result
13321324
elif isinstance(obj, dict):
1325+
if hasattr(type(obj), 'default_factory'):
1326+
# obj is a defaultdict, which has a different constructor from
1327+
# dict as it requires the default_factory as its first arg.
1328+
result = type(obj)(getattr(obj, 'default_factory'))
1329+
for k, v in obj.items():
1330+
result[_asdict_inner(k, dict_factory)] = _asdict_inner(v, dict_factory)
1331+
return result
13331332
return type(obj)((_asdict_inner(k, dict_factory),
13341333
_asdict_inner(v, dict_factory))
13351334
for k, v in obj.items())
@@ -1382,7 +1381,15 @@ def _astuple_inner(obj, tuple_factory):
13821381
# above).
13831382
return type(obj)(_astuple_inner(v, tuple_factory) for v in obj)
13841383
elif isinstance(obj, dict):
1385-
return type(obj)((_astuple_inner(k, tuple_factory), _astuple_inner(v, tuple_factory))
1384+
obj_type = type(obj)
1385+
if hasattr(obj_type, 'default_factory'):
1386+
# obj is a defaultdict, which has a different constructor from
1387+
# dict as it requires the default_factory as its first arg.
1388+
result = obj_type(getattr(obj, 'default_factory'))
1389+
for k, v in obj.items():
1390+
result[_astuple_inner(k, tuple_factory)] = _astuple_inner(v, tuple_factory)
1391+
return result
1392+
return obj_type((_astuple_inner(k, tuple_factory), _astuple_inner(v, tuple_factory))
13861393
for k, v in obj.items())
13871394
else:
13881395
return copy.deepcopy(obj)

Lib/test/test_dataclasses.py

+17-4
Original file line numberDiff line numberDiff line change
@@ -1706,19 +1706,17 @@ class C:
17061706
def test_helper_asdict_defaultdict(self):
17071707
# Ensure asdict() does not throw exceptions when a
17081708
# defaultdict is a member of a dataclass
1709-
17101709
@dataclass
17111710
class C:
17121711
mp: DefaultDict[str, List]
17131712

1714-
17151713
dd = defaultdict(list)
17161714
dd["x"].append(12)
17171715
c = C(mp=dd)
17181716
d = asdict(c)
17191717

1720-
assert d == {"mp": {"x": [12]}}
1721-
assert d["mp"] is not c.mp # make sure defaultdict is copied
1718+
self.assertEqual(d, {"mp": {"x": [12]}})
1719+
self.assertTrue(d["mp"] is not c.mp) # make sure defaultdict is copied
17221720

17231721
def test_helper_astuple(self):
17241722
# Basic tests for astuple(), it should return a new tuple.
@@ -1847,6 +1845,21 @@ class C:
18471845
t = astuple(c, tuple_factory=list)
18481846
self.assertEqual(t, ['outer', T(1, ['inner', T(11, 12, 13)], 2)])
18491847

1848+
def test_helper_astuple_defaultdict(self):
1849+
# Ensure astuple() does not throw exceptions when a
1850+
# defaultdict is a member of a dataclass
1851+
@dataclass
1852+
class C:
1853+
mp: DefaultDict[str, List]
1854+
1855+
dd = defaultdict(list)
1856+
dd["x"].append(12)
1857+
c = C(mp=dd)
1858+
t = astuple(c)
1859+
1860+
self.assertEqual(t, ({"x": [12]},))
1861+
self.assertTrue(t[0] is not dd) # make sure defaultdict is copied
1862+
18501863
def test_dynamic_class_creation(self):
18511864
cls_dict = {'__annotations__': {'x': int, 'y': int},
18521865
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :func:`dataclasses.astuple` crash when :class:`collections.defaultdict`
2+
is present in the attributes.

0 commit comments

Comments
 (0)