有没有特定的情况,使得Python >3.6.1中的dict()无序?

25 浏览
0 Comments

有没有特定的情况,使得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 的插入顺序不变

admin 更改状态以发布 2023年5月23日
0
0 Comments

以下是回答原始的第一个问题:

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

我认为文档中的这句话已经足以回答你的问题:

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

dict并没有明确表示它是一个有序的集合,因此如果你想保持一致性并不依赖新实现的副作用,你应该坚持使用OrderedDict

让您的代码更加具有未来性:)

这里有一个关于这个问题的辩论

编辑:Python 3.7将保持其作为一个特性请参阅

0
0 Comments

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_t1字节)到int32_t / int64_t4 / 8字节)在32 / 64位构建上)。

在之前的实现中,需要分配一个类型为PyDictKeyEntry且大小为dk_size 的稀疏数组;不幸的是,由于出于性能原因,这个数组最多只能被填充到2/3 * dk_size以免出现很多的空间(并且空间大小仍然为PyDictKeyEntry大小)

现在不再这样,只储存必需条目(插入的那些),并且维护一个类型为intX_tX取决于字典大小)并填充为 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]:新的字典实现以更紧凑的设计更好地执行内存方面;这是主要的好处。从速度上看,差别并不那么明显,有些地方,新字典可能会在(例如键查找)引入轻微的回归,而在其他方面(迭代和调整大小)可能会出现性能提升。


总的来说,在实际情况下,字典的性能由于引入了紧凑性而提高了。

0