为什么 `dataclasses.asdict(obj)` 比 `obj.__dict__()` 慢 10 倍
Why is `dataclasses.asdict(obj)` > 10x slower than `obj.__dict__()`
我正在使用 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
在大多数情况下,如果没有 dataclasses
,您会使用 __dict__
,您可能应该继续使用 __dict__
,也许需要调用 copy
。 asdict
做了 很多 的额外工作,您可能实际上并不想要。这是它的作用。
首先,来自docs:
Each dataclass is converted to a dict of its fields, as name: value pairs. dataclasses, dicts, lists, and tuples are recursed into. For example:
@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
构建一个 new 字典,而 __dict__
只是直接访问对象的属性字典。 asdict
的 return 值不会受到重新分配原始对象字段的影响。此外,asdict
使用 fields
,因此如果您将属性添加到与声明的字段不对应的数据类实例,asdict
将不会包含它们。
最后,文档根本没有提到它,但是 asdict
将 call 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
我正在使用 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
在大多数情况下,如果没有 dataclasses
,您会使用 __dict__
,您可能应该继续使用 __dict__
,也许需要调用 copy
。 asdict
做了 很多 的额外工作,您可能实际上并不想要。这是它的作用。
首先,来自docs:
Each dataclass is converted to a dict of its fields, as name: value pairs. dataclasses, dicts, lists, and tuples are recursed into. For example:
@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
构建一个 new 字典,而 __dict__
只是直接访问对象的属性字典。 asdict
的 return 值不会受到重新分配原始对象字段的影响。此外,asdict
使用 fields
,因此如果您将属性添加到与声明的字段不对应的数据类实例,asdict
将不会包含它们。
最后,文档根本没有提到它,但是 asdict
将 call 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