Python 3 - 访问数据哪个更快:dataclasses 还是字典?

19

Python 3.7引入了dataclasses来存储数据。我正在考虑采用这种新的方法,它比字典更有组织和结构。

但我有一个疑问。Python将字典中的键转换为哈希值,这使得查找键和值更快。数据类实现了类似的功能吗?

哪个更快,为什么?

(Note: Retaining all HTML tags as requested)
3个回答

47

实际上,Python中的所有类都在幕后使用字典来存储它们的属性,您可以在此处的文档中阅读到这一点。关于Python类(以及许多其他内容)如何工作的更详细参考资料,您还可以查看有关Python数据模型的文章,特别是有关自定义类的部分。

因此,通常情况下,从字典转移到数据类不应该导致性能损失。但最好使用timeit模块进行确认:


基准测试

# dictionary creation
$ python -m timeit "{'var': 1}"
5000000 loops, best of 5: 52.9 nsec per loop

# dictionary key access
$ python -m timeit -s "d = {'var': 1}" "d['var']"
10000000 loops, best of 5: 20.3 nsec per loop

基本数据类

# dataclass creation
$ python -m timeit -s "from dataclasses import dataclass" -s "@dataclass" -s "class A: var: int" "A(1)" 
1000000 loops, best of 5: 288 nsec per loop

# dataclass attribute access
$ python -m timeit -s "from dataclasses import dataclass" -s "@dataclass" -s "class A: var: int" -s "a = A(1)" "a.var" 
10000000 loops, best of 5: 25.3 nsec per loop

在这里,我们可以看到使用类确实会有一些额外的开销。对于类的创建来说,它比较慢(大约慢了5倍),但只要您不打算每秒钟创建和丢弃数据类,那么您并不一定需要过于关注它。

属性访问可能是更重要的度量标准,而数据类再次慢一些(大约1.25倍),但这次差距并不大。

如果您认为速度仍然有点慢,您可以通过使用slots来调整您的数据类(或任何类)代替使用字典来存储它们的属性:


带slot的数据类

# dataclass creation
$ python -m timeit -s "from dataclasses import dataclass" -s "@dataclass" -s "class A: __slots__ = ('var',); var: int" "A(1)" 
1000000 loops, best of 5: 242 nsec per loop

# dataclass attribute access
$ python -m timeit -s "from dataclasses import dataclass" -s "@dataclass" -s "class A: __slots__ = ('var',); var: int" -s "a = A(1)" "a.var"
10000000 loops, best of 5: 21.7 nsec per loop

通过使用这种模式,我们可以再节省几个纳秒。至少在属性访问方面,与字典相比,不应该再有明显的差异,您可以使用数据类的优点而不会影响速度。


3
谢谢你的答复!非常简明扼要地解决了我所有的疑问! - sergiomafra
1
@SérgioMafra 很高兴能帮到你 =) - Arne
3
尝试从字典列表创建10,000个数据类。这将耗费很长时间,访问不是问题,问题在于创建速度非常慢。 - Mejmo
5
Python3.10现在支持@dataclass(slots=True)!这模拟了示例中展示的slotted dataclass的功能。使用 python -m timeit -s "from dataclasses import dataclass" -s "@dataclass(slots=True)" -s "class A: var: int" "A(1)" 进行创建,使用 python -m timeit -s "from dataclasses import dataclass" -s "@dataclass(slots=True)" -s "class A: var: int" -s "a = A(1)" "a.var" 进行访问,时间与您的slotted dataclass示例相同。 - Trevor Gross

8

@Arne提供了一个出色的答案,并证明字典确实是两者中更快的。让我补充一些内容:

正如我在这里的评论中提到的,从Python 3.10开始,有@dataclass(slots = True)选项,可以创建具有插槽成员的数据类,与Arne示例中的更快的方法完全相同。除非您知道自己需要它,否则没有什么理由不使用slots = True

现在转向另一个较少为人知的选择。您可能会选择数据类而不是字典的主要原因之一是为了IDE提示(例如intellisense)和检查所期望的键是否存在。自Python 3.8以来,已经有了PEP589 TypedDict,它允许使用字典的标准格式进行此操作。请考虑以下内容:

from typing import TypedDict

class Movie(TypedDict):
    name: str
    year: int

movie: Movie = {'name': 'Blade Runner',
                'year': 1982}

在这种情况下,你的IDE将能够提示哪些键是有效的,并显示正确的初始化函数:

IDE screenshot access IDE screenshot init

此外,mypy将能够告诉您是否存在关键访问错误;更或者说,TypedDict可以为您提供一些重要的dataclass优势,而不使用dataclasses。总体而言,在您已经使用字典或仍需要像易于嵌套和略微更好的性能等字典功能时,这是一个很好的解决方案。请参阅上述PEP链接以获取大量良好的示例。
* 性能数字微不足道——如果dataclasses使您的生活更轻松,请使用它们。不要过早地优化到不确定的东西。太多的程序员试图削减纳秒而使自己的工作变得更加困难,而不是看一下他们的代码正在做什么的大局。

4
虽然我非常喜欢dataclasses,因为它们通常会导致更优雅的案例,但性能差异实际上可能是巨大的。 最近,我们重构了一个使用字典的数据处理应用程序,改用数据类,结果吞吐量下降了100倍以上。以前只需要毫秒处理的有效载荷现在需要几秒钟。
代码并没有做任何特别复杂的事情,但确实需要在不同的数据结构之间映射各个条目。运行分析表明,几乎所有执行时间都被各种内置数据类方法所占用(特别是_asdict_inner(),占总时间的约30%),因为每当进行任何数据操作时都会执行这些方法,例如将一个结构合并到另一个结构中。使用带槽的数据类只会导致约10%的速度提升。我相信其他改进也可能是可行的,但差距如此之大,以至于似乎不值得做这些改进。
我们切换回使用 TypedDicts,性能恢复到原始水平。 TypedDicts没有dataclasses的所有好处(例如类型检查和运行时强制执行),但在任何程度上对性能敏感的应用程序中,这种权衡似乎是一个明显的选择。

7
我认为人们会对这个案例、性能分析等内容感兴趣。如果你可以在这篇文章中增加一些像其他文章那样的示例,那将是一个巨大的加分项。 - tony
请问你能分享一下分析结果吗?我也很想看看,谢谢。 - Miroslav Karpíšek
我真的很希望这篇文章能够成为一个对照点,展示出我所展示的时间不覆盖重要部分的使用情况。然而,现在这只不过是一种挥手示意,并指向所谓的分析结果,这些结果可能或可能不显示你所描述的情况。是的,如果你使用数据类而不是字典,只是为了再次将它们转换为字典(因为这就是“_asdict_inner”所做的),那么你会浪费CPU。可以说,修复方法也可以是使用一个与“asdict”不同的函数来合并你的数据。 - undefined
话虽如此,我并不固守于数据类。如果你不需要完整的类,那么类型字典是很好的选择,它们可能会更适合你。因为类型字典也支持类型检查,而数据类在运行时不进行类型强制。这就是attrspydantic - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接