【问题标题】:Why is `dataclasses.asdict(obj)` > 10x slower than `obj.__dict__()`为什么 `dataclasses.asdict(obj)` > 10x 比 `obj.__dict__()` 慢
【发布时间】:2019-02-13 05:16:05
【问题描述】:

我正在使用 Python 3.6 和来自 ericvsmithdataclasses backport 包。

似乎调用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

【问题讨论】:

  • 好吧,对于初学者来说,asdict 将创建并返回 new dict object,并递归并将任何其他数据类实例转换为 dicts,而 __dict__ 只是返回对对象命名空间的引用,你可能不想改变的东西,例如...

标签: python python-dataclasses


【解决方案1】:

在大多数情况下,如果没有dataclasses,您会使用__dict__,那么您可能应该继续使用__dict__,也许可以使用copy 调用。 asdict 做了很多你可能并不真正想要的额外工作。这就是它的作用。


首先,来自docs

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

@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 的返回值不会受到重新分配原始对象字段的影响。此外,asdict 使用 fields,因此如果您向数据类实例添加与声明的字段不对应的属性,asdict 将不会包含它们。

最后,文档根本没有提到它,但 asdictcall 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

【讨论】:

  • 好的,这是有道理的。我想我只是在寻找一种“pythonic”方式将数据类转换为字典,而不依赖于像__dict__ 这样的属性。我猜vars(my_data_class) 做到了。
  • @ostrokach 不要那样做。它不会转换为字典返回对象的命名空间字典
  • 我的用例是df = pd.DataFrame([vars(dc) for dc in dcs]),所以最终会复制一份。但总的来说,是的,你是对的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-07
  • 2014-08-19
  • 1970-01-01
  • 2021-12-21
  • 1970-01-01
  • 2014-09-03
相关资源
最近更新 更多