Skip to content

Commit 0a8b596

Browse files
keithasaurusGlyphack
authored andcommitted
pythongh-114087: Speed up dataclasses._asdict_inner (python#114088)
1 parent 53347d7 commit 0a8b596

File tree

2 files changed

+56
-45
lines changed

2 files changed

+56
-45
lines changed

Diff for: Lib/dataclasses.py

+55-45
Original file line numberDiff line numberDiff line change
@@ -1332,58 +1332,69 @@ class C:
13321332

13331333

13341334
def _asdict_inner(obj, dict_factory):
1335-
if type(obj) in _ATOMIC_TYPES:
1335+
obj_type = type(obj)
1336+
if obj_type in _ATOMIC_TYPES:
13361337
return obj
1337-
elif _is_dataclass_instance(obj):
1338-
# fast path for the common case
1338+
elif hasattr(obj_type, _FIELDS):
1339+
# dataclass instance: fast path for the common case
13391340
if dict_factory is dict:
13401341
return {
13411342
f.name: _asdict_inner(getattr(obj, f.name), dict)
13421343
for f in fields(obj)
13431344
}
13441345
else:
1345-
result = []
1346-
for f in fields(obj):
1347-
value = _asdict_inner(getattr(obj, f.name), dict_factory)
1348-
result.append((f.name, value))
1349-
return dict_factory(result)
1350-
elif isinstance(obj, tuple) and hasattr(obj, '_fields'):
1351-
# obj is a namedtuple. Recurse into it, but the returned
1352-
# object is another namedtuple of the same type. This is
1353-
# similar to how other list- or tuple-derived classes are
1354-
# treated (see below), but we just need to create them
1355-
# differently because a namedtuple's __init__ needs to be
1356-
# called differently (see bpo-34363).
1357-
1358-
# I'm not using namedtuple's _asdict()
1359-
# method, because:
1360-
# - it does not recurse in to the namedtuple fields and
1361-
# convert them to dicts (using dict_factory).
1362-
# - I don't actually want to return a dict here. The main
1363-
# use case here is json.dumps, and it handles converting
1364-
# namedtuples to lists. Admittedly we're losing some
1365-
# information here when we produce a json list instead of a
1366-
# dict. Note that if we returned dicts here instead of
1367-
# namedtuples, we could no longer call asdict() on a data
1368-
# structure where a namedtuple was used as a dict key.
1369-
1370-
return type(obj)(*[_asdict_inner(v, dict_factory) for v in obj])
1371-
elif isinstance(obj, (list, tuple)):
1372-
# Assume we can create an object of this type by passing in a
1373-
# generator (which is not true for namedtuples, handled
1374-
# above).
1375-
return type(obj)(_asdict_inner(v, dict_factory) for v in obj)
1376-
elif isinstance(obj, dict):
1377-
if hasattr(type(obj), 'default_factory'):
1346+
return dict_factory([
1347+
(f.name, _asdict_inner(getattr(obj, f.name), dict_factory))
1348+
for f in fields(obj)
1349+
])
1350+
# handle the builtin types first for speed; subclasses handled below
1351+
elif obj_type is list:
1352+
return [_asdict_inner(v, dict_factory) for v in obj]
1353+
elif obj_type is dict:
1354+
return {
1355+
_asdict_inner(k, dict_factory): _asdict_inner(v, dict_factory)
1356+
for k, v in obj.items()
1357+
}
1358+
elif obj_type is tuple:
1359+
return tuple([_asdict_inner(v, dict_factory) for v in obj])
1360+
elif issubclass(obj_type, tuple):
1361+
if hasattr(obj, '_fields'):
1362+
# obj is a namedtuple. Recurse into it, but the returned
1363+
# object is another namedtuple of the same type. This is
1364+
# similar to how other list- or tuple-derived classes are
1365+
# treated (see below), but we just need to create them
1366+
# differently because a namedtuple's __init__ needs to be
1367+
# called differently (see bpo-34363).
1368+
1369+
# I'm not using namedtuple's _asdict()
1370+
# method, because:
1371+
# - it does not recurse in to the namedtuple fields and
1372+
# convert them to dicts (using dict_factory).
1373+
# - I don't actually want to return a dict here. The main
1374+
# use case here is json.dumps, and it handles converting
1375+
# namedtuples to lists. Admittedly we're losing some
1376+
# information here when we produce a json list instead of a
1377+
# dict. Note that if we returned dicts here instead of
1378+
# namedtuples, we could no longer call asdict() on a data
1379+
# structure where a namedtuple was used as a dict key.
1380+
return obj_type(*[_asdict_inner(v, dict_factory) for v in obj])
1381+
else:
1382+
return obj_type(_asdict_inner(v, dict_factory) for v in obj)
1383+
elif issubclass(obj_type, dict):
1384+
if hasattr(obj_type, 'default_factory'):
13781385
# obj is a defaultdict, which has a different constructor from
13791386
# dict as it requires the default_factory as its first arg.
1380-
result = type(obj)(getattr(obj, 'default_factory'))
1387+
result = obj_type(obj.default_factory)
13811388
for k, v in obj.items():
13821389
result[_asdict_inner(k, dict_factory)] = _asdict_inner(v, dict_factory)
13831390
return result
1384-
return type(obj)((_asdict_inner(k, dict_factory),
1385-
_asdict_inner(v, dict_factory))
1386-
for k, v in obj.items())
1391+
return obj_type((_asdict_inner(k, dict_factory),
1392+
_asdict_inner(v, dict_factory))
1393+
for k, v in obj.items())
1394+
elif issubclass(obj_type, list):
1395+
# Assume we can create an object of this type by passing in a
1396+
# generator
1397+
return obj_type(_asdict_inner(v, dict_factory) for v in obj)
13871398
else:
13881399
return copy.deepcopy(obj)
13891400

@@ -1416,11 +1427,10 @@ def _astuple_inner(obj, tuple_factory):
14161427
if type(obj) in _ATOMIC_TYPES:
14171428
return obj
14181429
elif _is_dataclass_instance(obj):
1419-
result = []
1420-
for f in fields(obj):
1421-
value = _astuple_inner(getattr(obj, f.name), tuple_factory)
1422-
result.append(value)
1423-
return tuple_factory(result)
1430+
return tuple_factory([
1431+
_astuple_inner(getattr(obj, f.name), tuple_factory)
1432+
for f in fields(obj)
1433+
])
14241434
elif isinstance(obj, tuple) and hasattr(obj, '_fields'):
14251435
# obj is a namedtuple. Recurse into it, but the returned
14261436
# object is another namedtuple of the same type. This is
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Speed up ``dataclasses.asdict`` up to 1.35x.

0 commit comments

Comments
 (0)