是否在Python 3.6+中订购了字典?


466

与以前的版本不同,字典在Python 3.6中排序(至少在CPython实现下)。这似乎是一个重大更改,但只是文档中的一小段。它被描述为CPython实现细节而不是语言功能,但这也意味着将来可能会成为标准。

在保留元素顺序的同时,新的字典实现如何比旧的实现更好?

以下是文档中的文字:

dict()现在使用PyPy率先提出的“紧凑”表示形式。与Python 3.5相比,新dict()的内存使用量减少了20%至25%。PEP 468(在函数中保留** kwarg的顺序。)由此实现。此新实现的顺序保留方面被认为是实现细节,因此不应依赖(将来可能会更改,但是希望在更改语言规范之前,先在几个发行版中使用该新dict实现该语言,为所有当前和将来的Python实现强制要求保留顺序的语义;这还有助于保留与仍旧有效的随机迭代顺序的旧版本语言(例如Python 3.5)的向后兼容性。(由INADA Naoki在发行27350最初由Raymond Hettinger提出的想法。)

2017年12月更新:Python 3.7 保证dict保留插入顺序


2
请参阅Python-Dev邮件列表上的以下线程:mail.python.org/pipermail/python-dev/2016-September/146327.html(如果尚未看到的话);基本上是围绕这些主题的讨论。
mgc

1
如果现在应该将kwargs排序(这是个好主意),而kwargs是dict,而不是OrderedDict,那么我猜可以假设dict键在Python的未来版本中将保持有序,尽管文档另有说明。
Dmitriy Sintsov

4
@DmitriySintsov不,不要做那个假设。这是在编写PEP时提出的一个问题,该问题定义了PEP的顺序保留功能,**kwargs因此使用的措辞是外交的:**kwargs在函数签名中,现在保证是保留插入顺序的映射。他们使用术语映射是为了不强制任何其他实现使dict有序(并在OrderedDict内部使用),并以此方式表明这不取决于d dict是无序的。
Dimitris Fasarakis Hilliard'2

7
Raymond Hettinger的精彩视频解释
Alex

1
@wazoox,哈希映射的顺序和复杂性没有改变。所做的更改通过浪费更少的空间来使哈希图更小,并且所节省的空间(通常是?)比辅助数组占用的空间更多。更快,更小,命-你拾取所有3
约翰·拉ROOY

Answers:


510

是否在Python 3.6+中订购了字典?

它们是插入顺序[1]。从Python 3.6开始,对于Python的CPython实现,字典会记住插入项目的顺序这在Python 3.6中被视为实现细节;你需要使用OrderedDict,如果你想多数民众赞成插入排序保证不同的Python的其它实现(与其他有序行为[1] )。

从Python 3.7开始,它不再是实现细节,而是成为一种语言功能。从GvR的py​​thon-dev消息中

做到这一点。裁定“裁定保留插入顺序”。谢谢!

这只是意味着您可以依靠它。如果其他Python实现希望成为Python 3.7的一致实现,则还必须提供插入顺序字典。


在保留元素顺序的同时,Python 3.6字典实现如何比旧的实现更好的性能[2]

本质上,通过保留两个数组

  • 第一个数组,按插入顺序dk_entries保存字典的条目(类型PyDictKeyEntry)。保留顺序是通过仅附加数组来实现的,在该数组中始终在末尾插入新项(插入顺序)。

  • 第二个dk_indices保留dk_entries数组的索引(即,指示中相应条目位置的值dk_entries)。该数组充当哈希表。对键进行哈希处理时,它会导致存储在其中的索引之一,dk_indices并且通过indexing获取相应的条目dk_entries。由于只有索引被保留,此数组的类型取决于字典的整体大小(范围从类型int8_t1字节)到int32_t/ int64_t4/ 8字节)上32/ 64位构建)

在以前的实现中,必须分配类型PyDictKeyEntry和大小的稀疏数组dk_size。不幸的是,由于性能原因,该阵列不允许2/3 * dk_size满载,这也导致了很多空白。(并且空白区域具有大小!)。PyDictKeyEntry

现在不是这种情况,因为仅存储了必需的条目(已插入的条目),并且保留了一个稀疏类型的数组intX_tX取决于dict的大小)2/3 * dk_size。空格从类型更改PyDictKeyEntryintX_t

因此,显然,创建一个类型PyDictKeyEntry稀疏的数组比存储ints 的稀疏数组需要更多的内存。

如果有兴趣,可以在Python-Dev上查看有关此功能的完整对话,这是一本好书。


在Raymond Hettinger提出的原始建议中,可以看到使用的数据结构的可视化效果,该可视化体现了该思想的要旨。

例如,字典:

d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}

当前存储为[keyhash,key,value]:

entries = [['--', '--', '--'],
           [-8522787127447073495, 'barry', 'green'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           [-9092791511155847987, 'timmy', 'red'],
           ['--', '--', '--'],
           [-6480567542315338377, 'guido', 'blue']]

相反,数据应按以下方式组织:

indices =  [None, 1, None, None, None, 0, None, 2]
entries =  [[-9092791511155847987, 'timmy', 'red'],
            [-8522787127447073495, 'barry', 'green'],
            [-6480567542315338377, 'guido', 'blue']]

正如您现在可以从视觉上看到的那样,在原始建议中,很多空间实际上是空的,以减少冲突并加快查找速度。使用新方法,可以通过将稀疏移动到真正需要的索引中来减少所需的内存。


[1]:我说“插入有序”而不是“有序”,因为在存在OrderedDict的情况下,“有序”暗示了dict对象不提供的其他行为。OrderedDicts是可逆的,提供顺序敏感的方法,并且主要是,提供一个订单sensive相等测试(==!=)。dict目前不提供任何这些行为/方法。


[2]:新的字典实现通过更紧凑的设计而在内存方面表现更好;这是这里的主要好处。在速度方面,差异并不那么明显,在某些地方,新的dict可能会引入轻微的回归(例如,关键查找),而在其他地方(会想到迭代和调整大小),应该会提高性能。

总体而言,由于引入的紧凑性,字典的性能(尤其是在现实生活中)得以提高。


15
那么,删除项目后会发生什么?在entries列表调整?还是保留空白?还是不时压缩?
njzk2

18
@ njzk2删除项目后,相应的索引将被替换为DKIX_DUMMY的值,-2entry数组中的条目将被替换为NULL,当执行插入操作时,新值将被附加到entrys数组中,还无法识别,但是可以肯定的是,当索引填充超过2/3阈值大小时,将执行调整。如果DUMMY存在许多条目,这可能导致收缩而不是增长。
Dimitris Fasarakis Hilliard

3
@Chris_Rands Nope,我看到的唯一实际回归是Victor消息中显示的跟踪器上。除了该微基准测试,我还没有看到其他问题/消息表明现实工作负荷中存在严重的速度差异。在某些情况下,新dict可能会引入轻微的回归(例如,对关键字进行查找),而在其他地方(会想到迭代和调整大小),则可能会提高性能。
Dimitris Fasarakis Hilliard

3
调整大小部分的更正:删除项目时,字典不会调整大小,而当您重新插入时,字典会重新计算。因此,如果创建了一个dict,d = {i:i for i in range(100)}并且您.pop没有插入所有项目,则大小不会改变。再次添加到时 d[1] = 1,将计算适当的大小,并调整dict的大小。
Dimitris Fasarakis Hilliard

6
@Chris_Rands我很确定它会留下来。问题是,我更改答案以删除关于“ dict被命令”的笼统声明的原因,即dicts在意义上没有被命令OrderedDict。值得注意的问题是平等。dicts对订单不敏感==OrderedDicts对订单不敏感。转储OrderedDicts并更改dicts为现在具有顺序敏感的比较可能会导致旧代码中的许多损坏。我猜测OrderedDicts 可能唯一改变的是它的实现。
Dimitris Fasarakis Hilliard '18

66

以下是回答最初的第一个问题:

我应该在Python 3.6中使用dict还是OrderedDict在Python 3.6中使用?

我认为文档中的这句话实际上足以回答您的问题

此新实现的顺序保留方面被视为实现细节,不应依赖于此

dict并不明确表示它是有序集合,因此,如果您要保持一致并且不依赖于新实现的副作用,则应坚持使用OrderedDict

使您的代码成为未来的证明:)

有关于辩论在这里

编辑:Python 3.7将保留此功能, 请参阅


1
似乎,如果他们不是说它是一个真正的功能,而只是实现细节,那么他们甚至不应该将其放入文档中。
xji

3
我不确定您的编辑注意事项;由于保证仅适用于Python 3.7,因此我认为Python 3.6的建议未更改,即dict是在CPython中排序的,但不要指望它
Chris_Rands

25

更新:Guido van Rossum 在邮件列表宣布,从 Python 3.7开始dict,所有Python实现中必须保留插入顺序。


2
既然键排序是官方标准,那么OrderedDict的目的是什么?或者,现在是否多余?
强尼华夫饼

2
我猜想OrderedDict不会是多余的,因为它具有move_to_end方法并且其相等性是顺序敏感的:docs.python.org/3/library/…。请参阅关于吉姆·法萨拉基斯·希利亚德(Jim Fasarakis Hilliard)答案的注释。
fjsj

@JonnyWaffles看到了吉姆的答案以及这个问答集stackoverflow.com/questions/50872498/…–
Chris_Rands

3
如果您想让代码在2.7和3.6 / 3.7 +上运行相同,则需要使用OrderedDict
boatcoder

3
出于安全原因而希望麻烦自己命令的人们可能很快就会有一个“ UnorderedDict”; p
ZF007

9

我想添加到上面的讨论中,但没有评论的声誉。

Python 3.8尚未发布,但它甚至将包含reversed()字典上的函数(消除了的另一个区别OrderedDict。)。

现在可以使用reversed()以反向插入顺序迭代Dict和dictviews。(由RémiLapeyre在bpo-33462中贡献。) 查看python 3.8的新增功能

我没有提到相等运算符或的其他功能,OrderedDict因此它们仍然不完全相同。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.