为什么 `dataclasses.asdict(obj)` 比 `obj.__dict__()` 慢 10 倍

问题描述 投票:0回答:2

我正在使用 Python 3.6 和来自

ericvsmith
dataclasses 向后移植包。

看来调用

dataclasses.asdict(my_dataclass)
比调用
my_dataclass.__dict__
慢约 10 倍:

In [172]: @dataclass
     ...: class MyDataClass:
     ...:     a: int
     ...:     b: int
     ...:     c: str
     ...: 

In [173]: %%time
     ...: _ = [MyDataClass(1, 2, "A" * 1000).__dict__ for _ in range(1_000_000)]
     ...: 
CPU times: user 631 ms, sys: 249 ms, total: 880 ms
Wall time: 880 ms

In [175]: %%time
     ...: _ = [dataclasses.asdict(MyDataClass(1, 2, "A" * 1000)) for _ in range(1_000_000)]
     ...: 
CPU times: user 11.3 s, sys: 328 ms, total: 11.6 s
Wall time: 11.7 s

这是预期的行为吗?在什么情况下我应该使用

dataclasses.asdict(obj)
而不是
obj.__dict__


编辑:使用

__dict__.copy()
并没有太大区别:

In [176]: %%time
     ...: _ = [MyDataClass(1, 2, "A" * 1000).__dict__.copy() for _ in range(1_000_000)]
     ...: 
CPU times: user 922 ms, sys: 48 ms, total: 970 ms
Wall time: 970 ms
python python-dataclasses
2个回答
76
投票

在大多数情况下,如果您在没有

__dict__
的情况下使用
dataclasses
,您可能应该继续使用
__dict__
,也许可以通过
copy
调用。
asdict
做了很多你可能并不真正想要的额外工作。这就是它的作用。

首先,来自
文档

每个数据类都转换为其字段的字典,作为名称:值对。
数据类、字典、列表和元组被递归成。

例如: @dataclass class Point: x: int y: int @dataclass class C: mylist: List[Point] p = Point(10, 20) assert asdict(p) == {'x': 10, 'y': 20} c = C([Point(0, 0), Point(10, 4)]) assert asdict(c) == {'mylist': [{'x': 0, 'y': 0}, {'x': 10, 'y': 4}]}

因此,如果您想要递归数据类听写,请使用 
asdict

。如果您不想要它,那么提供它的所有开销都被浪费了。特别是,如果您使用

asdict
,则将包含对象的实现更改为使用
dataclass
将更改
asdict
在外部对象上的结果。
递归逻辑也不处理循环引用。如果您使用数据类来表示图形或任何其他具有循环引用的数据结构,

asdict

将会崩溃:

import dataclasses

@dataclasses.dataclass
class GraphNode:
    name: str
    neighbors: list['GraphNode']

x = GraphNode('x', [])
y = GraphNode('y', [])
x.neighbors.append(y)
y.neighbors.append(x)

dataclasses.asdict(x) # crash here!

此示例中的 
asdict

调用会命中

RecursionError: maximum recursion depth exceeded while calling a Python object

除此之外,
asdict

构建了一个

new
字典,而__dict__只是直接访问对象的属性字典。
asdict
的返回值不会受到原始对象字段重新分配的影响。此外,
asdict
使用
fields
,因此,如果您向数据类实例添加与声明的字段不对应的属性,
asdict
将不会包含它们。
最后,文档根本没有提到它,但是

asdict

调用
deepcopy
对所有不是数据类对象、字典、列表或元组的东西:
else: return copy.deepcopy(obj)

(数据类对象、字典、列表和元组经过递归逻辑,这也构建了一个副本,只是应用了递归听写。)

deepcopy

本身确实非常昂贵,并且缺乏任何

memo
处理意味着
asdict
可能会在重要的对象图中创建共享对象的多个副本。请注意这一点:
>>> from dataclasses import dataclass, asdict
>>> @dataclass
... class Foo:
...     x: object
...     y: object
... 
>>> a = object()
>>> b = Foo(a, a)
>>> c = asdict(b)
>>> b.x is b.y
True
>>> c['x'] is c['y']
False
>>> c['x'] is b.x
False



0
投票

import dataclasses @dataclasses.dataclass class Item: o: str @dataclasses.dataclass class Obj: o: list[Item] print(Obj(o=[Item(o="1")]).__dict__) # {'o': [Item(o='1')]} print(dataclasses.asdict(Obj(o=[Item(o="1")]))) # {'o': [{'o': '1'}]}

© www.soinside.com 2019 - 2024. All rights reserved.