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

um6iljoc  于 2022-12-01  发布在  Python
关注(0)|答案(6)|浏览(188)

从Python 3.6开始,字典是按插入顺序排列的。它被描述为CPython实现细节,而不是语言特性。文档声明:
dict()现在使用“紧凑”表示pioneered by PyPy。()比Python 3.5小20%到25%。PEP 468(保留函数中**kwargs的顺序。)。此新实现的顺序保留方面被视为实现细节,不应依赖(这在未来可能会改变,但是在改变语言规范以强制所有当前和未来Python实现的顺序保持语义之前,希望在语言中有几个版本有这个新的dict实现;这也有助于保持与该语言的旧版本的向后兼容性,在旧版本中,随机迭代顺序仍然有效,例如Python 3.5)。(由INADA Naoki在issue 27350. Idea originally suggested by Raymond Hettinger中贡献。)
新的字典实现如何在保持元素顺序的同时比旧的字典实现性能更好?
2017年12月更新:dict的保留插入顺序对于Python 3.7是guaranteed

vjhs03f7

vjhs03f71#

Python 3.6+中的字典是否有序?

它们是插入顺序****[1]

从Python 3.6开始,对于Python的CPython实现,字典 * 记住了插入项的顺序 这被认为是Python 3.6中的实现细节 ;如果你想在Python的其他实现中 * 保证 * 插入顺序(和其他有序行为*[1]**),你需要使用OrderedDict
从Python 3.7开始,这是一个有保证的语言特性,而不仅仅是实现细节。From a python-dev message by GvR

就这样吧。“Dict保持插入顺序”是规则。谢谢!
这仅仅意味着 * 你可以依赖它 *。如果Python的其他实现希望成为Python 3.7的一致实现,它们也必须提供插入有序字典。

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

实际上,通过 * 保留两个数组 *。

  • 第一个数组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大小!)
现在情况不是这样,因为只存储 required 条目(已插入的条目),并保留intX_tX取决于字典大小)2/3 * dk_size s full类型的稀疏数组。空白空间从PyDictKeyEntry类型更改为intX_t
因此,很明显,创建PyDictKeyEntry类型的稀疏数组比存储int的稀疏数组需要更多的内存。
您可以看到关于这个特性的完整对话on Python-Dev如果感兴趣,这是一个很好的阅读。
In the original proposal made by 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]:我说“insertion ordered”而不是“ordered”是因为,在OrderedDict存在的情况下,“ordered”意味着'dict'对象 * 不提供 * 的进一步行为。OrderedDict是可逆的,提供顺序敏感的方法,主要是提供顺序敏感的等式测试('=','!=')。'dict'目前不提供任何这些行为/方法。[2]:新的字典实现通过被设计得更紧凑而在存储方面表现更好;这是这里的主要好处。速度方面,差别不是很大,新字典在某些地方可能会引入轻微的回归(例如键查找),而在其他地方(迭代和调整大小)则应该会出现性能提升。总的来说,字典的性能,特别是在现实生活中,由于引入了紧凑性而得到了改善。

sirbozc5

sirbozc52#

下面是对原第一个问题的回答:
我应该在Python 3.6中使用dict还是OrderedDict
我认为文档中的这句话实际上足以回答您的问题
此新实现的顺序保持方面被视为实现细节,不应依赖
dict并不是一个显式的有序集合,所以如果你想保持一致性,并且不依赖于新实现的副作用,你应该坚持使用OrderedDict
让您的代码经得起未来的考验:)
关于here有一个争论。
编辑:Python 3.7将保留此功能see

wfypjpf4

wfypjpf43#

更新:Guido van Rossum announced on the mailing list,自Python 3.7 dict起,所有Python实现中的s必须保持插入顺序。

c2e8gylq

c2e8gylq4#

我想补充到上面的讨论,但没有评论的声誉。
Python 3.8包含了字典上的reversed()函数(删除了与OrderedDict的另一个差异。
Dict和Dictview现在可以使用reversed()以相反的插入顺序进行迭代。(由Rémi Lapeyre贡献于bpo-33462。)See what's new in python 3.8
我没有看到任何关于OrderedDict的等式运算符或其他特性的内容,因此它们仍然不完全相同。

hmae6n7t

hmae6n7t5#

为了在2020年全面回答这个问题,让我引用Python官方文档中的几句话:
版本3.7中的变更:字典顺序保证为插入顺序。此行为是CPython 3.6的实现细节。
版本3.7中的变更:字典顺序保证为插入顺序。
版本3.8中的变更:字典现在是可逆的。
字典和字典视图是可逆的。
关于OrderedDict与Dict的声明:
有序字典就像常规字典一样,但是有一些额外的与排序操作相关的功能,它们已经变得不那么重要了,因为内置的dict类获得了记住插入顺序的能力(这个新的行为在Python 3.7中得到了保证)。

nue99wik

nue99wik6#

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

相关问题