字典的插入顺序从Python 3.6开始。它被描述为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的插入顺序


当前回答

字典在Python 3.6+中是有序的吗?

它们的插入顺序是[1]。

从Python 3.6开始,对于Python的CPython实现,字典会记住插入项的顺序。这被认为是Python 3.6中的实现细节;你需要使用OrderedDict,如果你想在其他Python实现(和其他有序行为[1])之间保证插入顺序。

从Python 3.7开始,这是一个有保证的语言特性,而不仅仅是一个实现细节。来自GvR的python-dev消息:

让它成为现实。“词典保持插入顺序”是裁决。谢谢!

这仅仅意味着您可以依赖它。Python的其他实现如果希望成为符合Python 3.7的实现,也必须提供一个插入顺序字典。


Python 3.6字典实现如何在保持元素顺序的同时比旧的[2]执行得更好?

本质上,通过保持两个数组。

The first array, dk_entries, holds the entries (of type PyDictKeyEntry) for the dictionary in the order that they were inserted. Preserving order is achieved by this being an append only array where new items are always inserted at the end (insertion order). The second, dk_indices, holds the indices for the dk_entries array (that is, values that indicate the position of the corresponding entry in dk_entries). This array acts as the hash table. When a key is hashed it leads to one of the indices stored in dk_indices and the corresponding entry is fetched by indexing dk_entries. Since only indices are kept, the type of this array depends on the overall size of the dictionary (ranging from type int8_t(1 byte) to int32_t/int64_t (4/8 bytes) on 32/64 bit builds)

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

现在不是这样了,因为只存储了必需的条目(那些已经插入的),并且保留了类型为intX_t (X取决于字典大小)2/3 * dk_sizes full的稀疏数组。空格类型从PyDictKeyEntry变为intX_t。

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

如果感兴趣,你可以在Python-Dev上看到关于这个特性的完整对话,这是一本不错的读物。


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

For example, the dictionary: d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'} is currently stored as [keyhash, key, value]: entries = [['--', '--', '--'], [-8522787127447073495, 'barry', 'green'], ['--', '--', '--'], ['--', '--', '--'], ['--', '--', '--'], [-9092791511155847987, 'timmy', 'red'], ['--', '--', '--'], [-6480567542315338377, 'guido', 'blue']] Instead, the data should be organized as follows: indices = [None, 1, None, None, None, 0, None, 2] entries = [[-9092791511155847987, 'timmy', 'red'], [-8522787127447073495, 'barry', 'green'], [-6480567542315338377, 'guido', 'blue']]

正如您现在可以直观地看到的,在最初的方案中,许多空间基本上是空的,以减少冲突并使查找更快。使用新方法,您可以通过将稀疏性移到真正需要的位置(即索引中)来减少所需的内存。


[1]:我说“插入有序”,而不是“有序”,因为OrderedDict的存在,“有序”暗示了‘dict’对象*不提供*的进一步行为。OrderedDicts是可逆的,提供顺序敏感的方法,主要是提供顺序敏感的相等性测试(' == ',' != ')。“字典目前没有提供任何这些行为/方法。


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

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

其他回答

下面是对第一个问题的回答:

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

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

这个新实现的顺序保持方面被认为是一个实现细节,不应该依赖它

dict并不是一个有序的集合,所以如果你想保持一致并且不依赖于新实现的副作用,你应该坚持使用OrderedDict。

让你的代码能够验证未来:)

这里有关于这个问题的争论。

编辑:Python 3.7将保留此功能

字典在Python 3.6+中是有序的吗?

它们的插入顺序是[1]。

从Python 3.6开始,对于Python的CPython实现,字典会记住插入项的顺序。这被认为是Python 3.6中的实现细节;你需要使用OrderedDict,如果你想在其他Python实现(和其他有序行为[1])之间保证插入顺序。

从Python 3.7开始,这是一个有保证的语言特性,而不仅仅是一个实现细节。来自GvR的python-dev消息:

让它成为现实。“词典保持插入顺序”是裁决。谢谢!

这仅仅意味着您可以依赖它。Python的其他实现如果希望成为符合Python 3.7的实现,也必须提供一个插入顺序字典。


Python 3.6字典实现如何在保持元素顺序的同时比旧的[2]执行得更好?

本质上,通过保持两个数组。

The first array, dk_entries, holds the entries (of type PyDictKeyEntry) for the dictionary in the order that they were inserted. Preserving order is achieved by this being an append only array where new items are always inserted at the end (insertion order). The second, dk_indices, holds the indices for the dk_entries array (that is, values that indicate the position of the corresponding entry in dk_entries). This array acts as the hash table. When a key is hashed it leads to one of the indices stored in dk_indices and the corresponding entry is fetched by indexing dk_entries. Since only indices are kept, the type of this array depends on the overall size of the dictionary (ranging from type int8_t(1 byte) to int32_t/int64_t (4/8 bytes) on 32/64 bit builds)

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

现在不是这样了,因为只存储了必需的条目(那些已经插入的),并且保留了类型为intX_t (X取决于字典大小)2/3 * dk_sizes full的稀疏数组。空格类型从PyDictKeyEntry变为intX_t。

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

如果感兴趣,你可以在Python-Dev上看到关于这个特性的完整对话,这是一本不错的读物。


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

For example, the dictionary: d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'} is currently stored as [keyhash, key, value]: entries = [['--', '--', '--'], [-8522787127447073495, 'barry', 'green'], ['--', '--', '--'], ['--', '--', '--'], ['--', '--', '--'], [-9092791511155847987, 'timmy', 'red'], ['--', '--', '--'], [-6480567542315338377, 'guido', 'blue']] Instead, the data should be organized as follows: indices = [None, 1, None, None, None, 0, None, 2] entries = [[-9092791511155847987, 'timmy', 'red'], [-8522787127447073495, 'barry', 'green'], [-6480567542315338377, 'guido', 'blue']]

正如您现在可以直观地看到的,在最初的方案中,许多空间基本上是空的,以减少冲突并使查找更快。使用新方法,您可以通过将稀疏性移到真正需要的位置(即索引中)来减少所需的内存。


[1]:我说“插入有序”,而不是“有序”,因为OrderedDict的存在,“有序”暗示了‘dict’对象*不提供*的进一步行为。OrderedDicts是可逆的,提供顺序敏感的方法,主要是提供顺序敏感的相等性测试(' == ',' != ')。“字典目前没有提供任何这些行为/方法。


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

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

我想补充上面的讨论,但没有声誉评论。

Python 3.8在字典上包含reversed()函数(删除了与OrderedDict的另一个区别)。

Dict和dictview现在可以使用reversed()以反向插入顺序迭代。(由Rémi Lapeyre在bpo-33462中贡献。) 查看python 3.8的新特性

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

在3.7版更改:字典顺序保证为插入顺序。此行为是CPython 3.6版本的实现细节。

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