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

Are dictionaries ordered in Python 3.6+?

字典在python 3.6中排序(至少在cpython实现下),与以前的版本不同。这似乎是一个实质性的变化,但它只是文档中的一小段。它被描述为一个cpython实现细节,而不是一个语言特性,但也意味着这可能成为未来的标准。

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

以下是文档中的文本:

dict() now uses a"compact" representation pioneered by PyPy. The memory usage of the new dict() is between 20% and 25% smaller compared to Python 3.5. PEP 468 (Preserving the order of **kwargs in a function.) is implemented by this. The order-preserving aspect of this new implementation is considered an implementation detail and should not be relied upon (this may change in the future, but it is desired to have this new dict implementation in the language for a few releases before changing the language spec to mandate order-preserving semantics for all current and future Python implementations; this also helps preserve backwards-compatibility with older versions of the language where random iteration order is still in effect, e.g. Python 3.5). (Contributed by INADA Naoki in issue 27350. Idea originally suggested by Raymond Hettinger.)

更新日期:2017年12月:保证为python 3.7提供dict的保留插入命令


Are dictionaries ordered in Python 3.6+?

它们是按插入顺序排列的[1]。从python 3.6开始,对于python的cpython实现,字典记住插入项的顺序。在Python3.6中,这被视为一个实现细节;如果您希望在其他Python实现中保证插入顺序(以及其他顺序行为[1]),则需要使用OrderedDict

从Python3.7开始,这不再是一个实现细节,而是一个语言特性。来自gvr的python dev消息:

Make it so."Dict keeps insertion order" is the ruling. Thanks!

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

How does the Python 3.6 dictionary implementation perform better[2] than the older one while preserving element order?

基本上,通过保留两个数组。

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

  • 第二个是dk_indices持有dk_entries数组的索引(即表示dk_entries中相应条目位置的值)。此数组用作哈希表。当一个键被散列时,它会导致存储在dk_indices中的一个索引,并通过索引dk_entries获取相应的条目。由于只保留索引,因此该数组的类型取决于字典的总体大小(从int8_t型(1字节)到int32_t/int64_t型(4/8字节)在32/64位构建上的范围)。

在以前的实现中,必须分配一个类型为PyDictKeyEntry和大小为dk_size的稀疏数组;不幸的是,由于性能原因,该数组不允许超过2/3 * dk_size的满空间,因此也导致了大量的空空间。(空的空间仍然有PyDictKeyEntry大小!).

现在情况并非如此,因为只存储所需的条目(已插入的条目),并保留一个类型为intX_t(X的稀疏数组(取决于dict大小),2/3 * dk_size已满。空位由PyDictKeyEntry型变为intX_t型。

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

您可以在python dev上看到关于这个特性的完整对话,如果您感兴趣,这是一个很好的阅读。

在RaymondHettinger最初提出的建议中,可以看到所使用的数据结构的可视化,它抓住了这个想法的要点。

For example, the dictionary:

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

is currently stored as:

1
2
3
4
5
6
7
8
entries = [['--', '--', '--'],
           [-8522787127447073495, 'barry', 'green'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           ['--', '--', '--'],
           [-9092791511155847987, 'timmy', 'red'],
           ['--', '--', '--'],
           [-6480567542315338377, 'guido', 'blue']]

Instead, the data should be organized as follows:

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

正如您现在看到的,在最初的建议中,很多空间基本上是空的,以减少碰撞并使查找更快。使用新方法,您可以通过移动索引中真正需要的稀疏度来减少所需的内存。

【1】:我说的是"插入有序"而不是"有序",因为"有序"的存在表明了dict对象不提供的进一步行为。顺序图是可逆的,提供顺序敏感的方法,主要提供顺序敏感的相等测试(==!=)。dict目前不提供任何这些行为/方法。

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

总的来说,字典的性能,特别是在现实生活中,由于引入了紧凑性而得到提高。


下面是第一个原始问题的答案:

Should I use dict or OrderedDict in Python 3.6?

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

The order-preserving aspect of this new implementation is considered an implementation detail and should not be relied upon

dict并非明确表示要进行有序收集,因此,如果您希望保持一致,而不依赖于新实现的副作用,则应坚持使用OrderedDict

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

这里有一个争论。

编辑:python 3.7将保留这一特性,请参见


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