有没有特定的情况,使得Python >3.6.1中的dict()无序?
有没有特定的情况,使得Python >3.6.1中的dict()无序?
Python 3.6 开始,字典是按插入顺序排序的。文档称这是 CPython 实现的一个细节,而不是语言特性。 文档 表示:
dict()
现在使用了 PyPy 开创的“紧凑”表示法。新版 dict() 的内存使用比 Python 3.5 小20%到25%。字典保持顺序是通过实现 PEP 468(在函数中保留 **kwargs 的顺序)来实现的。新实现的保持顺序是一个实现细节,不能依赖它(这可能会在将来更改,但希望在更改语言规范以要求所有当前和未来的 Python 实现保持顺序语义之前,在语言中保留这个新的 dict 实现数个版本;这也有助于保持向后兼容老版本语言,其中随机迭代顺序仍然有效,例如 Python 3.5)。 (由 INADA Naoki 在issue 27350 中做出贡献。最初由 Raymond Hettinger 建议,具体可参考此处)。
新的字典实现是如何在保留元素顺序的同时比旧实现更好的呢?
2017年12月更新:Python 3.7保证了 dict
的插入顺序不变
以下是回答原始的第一个问题:
在Python 3.6中,我应该使用
dict
还是OrderedDict
?
我认为文档中的这句话已经足以回答你的问题:
这个新实现的保持顺序的方面被认为是一个实施细节,不应该依赖于它
dict
并没有明确表示它是一个有序的集合,因此如果你想保持一致性并不依赖新实现的副作用,你应该坚持使用OrderedDict
。
让您的代码更加具有未来性:)
这里有一个关于这个问题的辩论。
编辑:Python 3.7将保持其作为一个特性,请参阅
Python 3.6+版本中的字典是否有序?
它们是按插入顺序排序[1]的。
截至Python 3.6版本,对于Python的CPython实现来说,字典记住了插入的顺序。但是在Python 3.6中,这被认为是一个实现细节;如果您想要跨Python(以及其他有序行为[1])的实现保证的插入顺序,您需要使用OrderedDict
。
从Python 3.7开始,这是一种确保的语言特性,而不仅仅是实现细节。GvR在一条python-dev信息中说:
使它成为实际情况。“字典保持插入顺序”是规定。感谢!
这只是意味着您可以信赖它。如果其他Python实现希望成为符合Python 3.7的实现,他们也必须提供一个插入排序的字典。
Python 3.6字典实现如何比旧版本更快且保留元素顺序?
基本上是通过保留两个数组。
-
第一个数组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
取决于字典大小)并填充为 2/3 * dk_size
的稀疏数组。空闲空间已从PyDictKeyEntry
更改为intX_t
。
因此,很明显,创建类型为PyDictKeyEntry
的稀疏数组比用于存储整数的稀疏数组内存需求更高。
如果感兴趣的话,您可以在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`对象*不提供*的其他行为。 OrderedDict是可逆的,提供有序敏感的方法和主要是提供有序敏感的相等测试(`==`,`!=`)。目前,dicts不提供任何这些行为/方法。
[2]:新的字典实现以更紧凑的设计更好地执行内存方面;这是主要的好处。从速度上看,差别并不那么明显,有些地方,新字典可能会在(例如键查找)引入轻微的回归,而在其他方面(迭代和调整大小)可能会出现性能提升。
总的来说,在实际情况下,字典的性能由于引入了紧凑性而提高了。