ChatGPT解决这个技术问题 Extra ChatGPT

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

从 Python 3.6 开始,字典是插入排序的。它被描述为 CPython 实现细节而不是语言特性。 documentation 指出:

dict() 现在使用由 PyPy 开创的“紧凑”表示。与 Python 3.5 相比,新 dict() 的内存使用量减少了 20% 到 25%。 PEP 468(在函数中保留 **kwargs 的顺序。)由此实现。这个新实现的顺序保留方面被认为是一个实现细节,不应依赖(这可能会在未来发生变化,但在更改语言规范之前,希望在几个版本中使用该语言的这个新 dict 实现为所有当前和未来的 Python 实现强制要求保持顺序的语义;这也有助于保持与随机迭代顺序仍然有效的旧版本语言的向后兼容性,例如 Python 3.5)。 (由 INADA Naoki 在 issue 27350 中提供。最初由 Raymond Hettinger 提出的想法。)

新字典实现如何在保留元素顺序的同时比旧字典实现更好?

2017 年 12 月更新:对于 Python 3.7,dict 的保留广告订单是 guaranteed

请参阅 Python-Dev 邮件列表上的此线程:mail.python.org/pipermail/python-dev/2016-September/146327.html 如果您还没有看到它;它基本上是围绕这些主题的讨论。
如果现在应该对 kwargs 进行排序(这是个好主意)并且 kwargs 是 dict,而不是 OrderedDict,那么我想人们可以假设 dict 键将在 Python 的未来版本中保持有序,尽管文档另有说明。
@DmitriySintsov 不,不要做这个假设。这是在编写定义 **kwargs 的顺序保留功能的 PEP 期间提出的一个问题,因此使用的措辞是外交的:现在保证函数签名中的 **kwargs 是插入顺序-保留映射。他们使用术语 mapping 是为了不强制任何其他实现使 dict 有序(并在内部使用 OrderedDict )并作为一种表示这不应该依赖的方式关于 dict 未排序的事实。
来自 Raymond Hettinger 的出色video explanation
@wazoox,哈希图的顺序和复杂性没有改变。该更改通过浪费更少的空间使哈希图更小,并且节省的空间(通常?)比辅助数组占用的空间多。更快、更小、更有序 - 您可以选择所有 3 个。

M
Mateen Ulhaq

字典是在 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

让它如此。 “字典保持插入顺序”是裁决。谢谢!

这仅仅意味着你可以依赖它。如果 Python 的其他实现希望成为 Python 3.7 的一致实现,它们还必须提供插入有序字典。

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

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

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

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

在之前的实现中,必须分配类型为 PyDictKeyEntry 且大小为 dk_size 的稀疏数组;不幸的是,这也导致了很多空白空间,因为该数组不允许超过 2/3 * dk_sizefor performance reasons。 (并且空白空间仍然PyDictKeyEntry 大小!)。

现在情况并非如此,因为仅存储 必需 条目(已插入的条目)和 intX_t 类型的稀疏数组(X 取决于 dict 大小)2/3 * dk_size 已满保持。空白区域从类型 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']] 相反,数据应按如下方式组织:indexes = [None, 1, None, None, None, 0, None, 2 ] 条目 = [[-9092791511155847987, 'timmy', 'red'], [-8522787127447073495, 'barry', 'green'], [-6480567542315338377, 'guido', 'blue']]

正如您现在可以直观地看到的那样,在最初的提议中,很多空间基本上是空的,以减少冲突并加快查找速度。使用新方法,您可以通过将稀疏性移动到索引中真正需要的位置来减少所需的内存。

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

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

总体而言,字典的性能,尤其是在现实生活中,由于引入了紧凑性而有所提高。


那么,当一个项目被删除时会发生什么? entries 列表是否已调整大小?还是保留一个空格?还是不时压缩?
@njzk2 当一个项目被删除时,相应的索引被 DKIX_DUMMY 替换为 -2entry 数组 replaced by NULL 中的条目,当执行插入时,新值被附加到条目数组, 还不能辨别,但很确定当索引填充超过 2/3 阈值时,会执行调整大小。如果存在许多 DUMMY 条目,这可能会导致缩小而不是增长。
@Chris_Rands 不,我看到的唯一实际回归是在 message by Victor 中的跟踪器上。除了那个微基准之外,我没有看到其他问题/消息表明现实生活中的工作负载存在严重的速度差异。在某些地方,新 dict 可能会引入轻微的回归(例如键查找),而在其他地方(想到迭代和调整大小)会出现性能提升。
调整大小部分的更正:字典不会在您删除项目时调整大小,当您重新插入时它们会重新计算。因此,如果使用 d = {i:i for i in range(100)} 创建了一个 dict,并且您 .pop 没有插入所有项目,则大小不会改变。当您再次添加 d[1] = 1 时,将计算适当的大小并调整 dict 的大小。
@Chris_Rands 我很确定它会留下来。问题是,我之所以更改答案以删除关于“dict 被订购”的笼统陈述,dict 的顺序与 OrderedDict 的顺序不同。值得注意的问题是平等。 dict 有顺序不敏感的 ==OrderedDict 有顺序敏感的。转储 OrderedDict 并将 dicts 更改为现在具有顺序敏感比较可能会导致旧代码出现大量损坏。我猜想OrderedDicts 唯一可能改变的是它的实现。
M
Maresh

以下是回答最初的第一个问题:

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

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

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

dict 并不明确意味着是一个有序集合,因此如果您想保持一致并且不依赖于新实现的副作用,您应该坚持使用 OrderedDict

让你的代码面向未来:)

关于该here存在争议。

编辑:Python 3.7 将保留此功能 see


D
Dimitris Fasarakis Hilliard

更新:Guido van Rossum announced on the mailing list 从 Python 3.7 dict 开始,所有 Python 实现都必须保留插入顺序。


既然密钥排序是官方标准,那么 OrderedDict 的目的是什么?或者,它现在是多余的吗?
我猜 OrderedDict 不会是多余的,因为它有 move_to_end 方法并且它的相等性是顺序敏感的:docs.python.org/3/library/…。请参阅有关 Jim Fasarakis Hilliard 答案的注释。
@JonnyWaffles 看到 Jim 的回答和这个问答stackoverflow.com/questions/50872498/…
如果您希望您的代码在 2.7 和 3.6/3.7+ 上运行相同,则需要使用 OrderedDict
可能很快就会有一个“UnorderedDict”,供那些出于安全原因喜欢麻烦他们的听写的人;p
G
Gulzar

我想添加到上面的讨论中,但没有评论的声誉。

Python 3.8 在字典中包含 reversed() 函数(删除了与 OrderedDict.

现在可以使用 reversed() 以反向插入顺序迭代字典和字典视图。 (由 Rémi Lapeyre 在 bpo-33462 中贡献。)查看 python 3.8 中的新功能

我没有看到任何提及相等运算符或 OrderedDict 的其他功能,因此它们仍然不完全相同。


P
Peng

为了在 2020 年全面回答这个问题,我引用 official Python docs 中的几句话:

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

在 3.7 版更改: 字典顺序保证为插入顺序。

在 3.8 版更改: 字典现在是可逆的。

字典和字典视图是可逆的。

关于 OrderedDict 与 Dict 的statement

有序词典就像普通词典一样,但具有一些与排序操作相关的额外功能。现在它们变得不那么重要了,因为内置的 dict 类获得了记住插入顺序的能力(这种新行为在 Python 3.7 中得到了保证)。


C
CN-LanBao

如果 order 对判断两个字典是否相等非常重要,您仍然需要使用 OrderedDict
为了实现此功能,OrderedDict 使用更多内存(内部维护双向链表)

from collections import OrderedDict

a = {"a": 1, "b": 2}
b = {"b": 2, "a": 1}
c = OrderedDict({"a": 1, "b": 2})
d = OrderedDict({"b": 2, "a": 1})

print(a == b)  # True
print(c == d)  # False
print(a == c)  # True
print(a == d)  # True