diff options
author | Eric V. Smith <ericvsmith@users.noreply.github.com> | 2018-09-14 15:32:16 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-09-14 15:32:16 (GMT) |
commit | 9b9d97dd139a799d28ff8bc90d118b1cac190b03 (patch) | |
tree | bfd25599bae88aa025341eb7aceba972185c6af8 /Lib/dataclasses.py | |
parent | 73820a60cc3c990abb351540ca27bf7689bce8ac (diff) | |
download | cpython-9b9d97dd139a799d28ff8bc90d118b1cac190b03.zip cpython-9b9d97dd139a799d28ff8bc90d118b1cac190b03.tar.gz cpython-9b9d97dd139a799d28ff8bc90d118b1cac190b03.tar.bz2 |
bpo-34363: dataclasses.asdict() and .astuple() now handle fields which are namedtuples. (GH-9151)
Diffstat (limited to 'Lib/dataclasses.py')
-rw-r--r-- | Lib/dataclasses.py | 40 |
1 files changed, 38 insertions, 2 deletions
diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index a43d076..28e9f75 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -1026,11 +1026,36 @@ def _asdict_inner(obj, dict_factory): value = _asdict_inner(getattr(obj, f.name), dict_factory) result.append((f.name, value)) return dict_factory(result) + elif isinstance(obj, tuple) and hasattr(obj, '_fields'): + # obj is a namedtuple. Recurse into it, but the returned + # object is another namedtuple of the same type. This is + # similar to how other list- or tuple-derived classes are + # treated (see below), but we just need to create them + # differently because a namedtuple's __init__ needs to be + # called differently (see bpo-34363). + + # I'm not using namedtuple's _asdict() + # method, because: + # - it does not recurse in to the namedtuple fields and + # convert them to dicts (using dict_factory). + # - I don't actually want to return a dict here. The the main + # use case here is json.dumps, and it handles converting + # namedtuples to lists. Admittedly we're losing some + # information here when we produce a json list instead of a + # dict. Note that if we returned dicts here instead of + # namedtuples, we could no longer call asdict() on a data + # structure where a namedtuple was used as a dict key. + + return type(obj)(*[_asdict_inner(v, dict_factory) for v in obj]) elif isinstance(obj, (list, tuple)): + # Assume we can create an object of this type by passing in a + # 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): - return type(obj)((_asdict_inner(k, dict_factory), _asdict_inner(v, dict_factory)) - for k, v in obj.items()) + return type(obj)((_asdict_inner(k, dict_factory), + _asdict_inner(v, dict_factory)) + for k, v in obj.items()) else: return copy.deepcopy(obj) @@ -1066,7 +1091,18 @@ def _astuple_inner(obj, tuple_factory): value = _astuple_inner(getattr(obj, f.name), tuple_factory) result.append(value) return tuple_factory(result) + elif isinstance(obj, tuple) and hasattr(obj, '_fields'): + # obj is a namedtuple. Recurse into it, but the returned + # object is another namedtuple of the same type. This is + # similar to how other list- or tuple-derived classes are + # treated (see below), but we just need to create them + # differently because a namedtuple's __init__ needs to be + # called differently (see bpo-34363). + return type(obj)(*[_astuple_inner(v, tuple_factory) for v in obj]) elif isinstance(obj, (list, tuple)): + # Assume we can create an object of this type by passing in a + # generator (which is not true for namedtuples, handled + # above). return type(obj)(_astuple_inner(v, tuple_factory) for v in obj) elif isinstance(obj, dict): return type(obj)((_astuple_inner(k, tuple_factory), _astuple_inner(v, tuple_factory)) |