From e696e23e727f1c99ec639d3cccf3e82d73e94576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Lapeyre?= Date: Sun, 30 Dec 2018 01:44:20 +0100 Subject: [PATCH 1/2] Add collections.defaultdict support to dataclasses.{asdict,astuple} --- Lib/dataclasses.py | 13 ++++++++++++ Lib/test/test_dataclasses.py | 20 +++++++++++++++++++ .../2018-12-30-01-53-59.bpo-35540.sL32q2.rst | 2 ++ 3 files changed, 35 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2018-12-30-01-53-59.bpo-35540.sL32q2.rst diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 71d9896a10524a..9c6b314bba80cb 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -6,6 +6,7 @@ import keyword import builtins import functools +import collections import _thread @@ -1077,6 +1078,12 @@ 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, collections.defaultdict): + # defaultdict does not have the same constructor than dict and must be + # hendled separately + return type(obj)(obj.default_factory, ((_asdict_inner(k, dict_factory), + _asdict_inner(v, dict_factory)) + for k, v in obj.items())) elif isinstance(obj, dict): return type(obj)((_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory)) @@ -1129,6 +1136,12 @@ def _astuple_inner(obj, tuple_factory): # generator (which is not true for namedtuples, handled # above). return type(obj)(_astuple_inner(v, tuple_factory) for v in obj) + elif isinstance(obj, collections.defaultdict): + # defaultdict does not have the same constructor than dict and must be + # hendled separately + return type(obj)(obj.default_factory, ((_asdict_inner(k, dict_factory), + _asdict_inner(v, dict_factory)) + for k, v in obj.items())) elif isinstance(obj, dict): return type(obj)((_astuple_inner(k, tuple_factory), _astuple_inner(v, tuple_factory)) for k, v in obj.items()) diff --git a/Lib/test/test_dataclasses.py b/Lib/test/test_dataclasses.py index ff6060c6d2838a..45a51571de8002 100755 --- a/Lib/test/test_dataclasses.py +++ b/Lib/test/test_dataclasses.py @@ -1493,6 +1493,17 @@ class C: self.assertIsNot(d['f'], t) self.assertEqual(d['f'].my_a(), 6) + def test_helper_asdict_defaultdict(self): + @dataclass + class C: + d: dict + + from collections import defaultdict + c = C(defaultdict(int)) + self.assertEqual(asdict(c), { + 'd': defaultdict(int) + }) + def test_helper_astuple(self): # Basic tests for astuple(), it should return a new tuple. @dataclass @@ -1620,6 +1631,15 @@ 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): + @dataclass + class C: + d: dict + + from collections import defaultdict + c = C(defaultdict(int)) + self.assertEqual(astuple(c), (defaultdict(int),)) + def test_dynamic_class_creation(self): cls_dict = {'__annotations__': {'x': int, 'y': int}, } diff --git a/Misc/NEWS.d/next/Library/2018-12-30-01-53-59.bpo-35540.sL32q2.rst b/Misc/NEWS.d/next/Library/2018-12-30-01-53-59.bpo-35540.sL32q2.rst new file mode 100644 index 00000000000000..667d0a303ba9b1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-12-30-01-53-59.bpo-35540.sL32q2.rst @@ -0,0 +1,2 @@ +`dataclasses.asdict` and `dataclasses.astuple` now accept instances with +`collections.defaultdict` attributes. Patch by Rémi Lapeyre. From 86f42a18fcf223cdaad578d503b1b8e10c32cbdb Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sun, 30 Dec 2018 17:57:20 +0100 Subject: [PATCH 2/2] Fix typo in comments Co-Authored-By: remilapeyre --- Lib/dataclasses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 9c6b314bba80cb..1a52341ea49769 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1080,7 +1080,7 @@ def _asdict_inner(obj, dict_factory): return type(obj)(_asdict_inner(v, dict_factory) for v in obj) elif isinstance(obj, collections.defaultdict): # defaultdict does not have the same constructor than dict and must be - # hendled separately + # handled separately return type(obj)(obj.default_factory, ((_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory)) for k, v in obj.items())) @@ -1138,7 +1138,7 @@ def _astuple_inner(obj, tuple_factory): return type(obj)(_astuple_inner(v, tuple_factory) for v in obj) elif isinstance(obj, collections.defaultdict): # defaultdict does not have the same constructor than dict and must be - # hendled separately + # handled separately return type(obj)(obj.default_factory, ((_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory)) for k, v in obj.items()))