为什么Python集不保留插入顺序?


12

最近我很惊讶地发现,虽然保证字典可以保留Python 3.7+中的插入顺序,但集合却不能:

>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d
{'a': 1, 'b': 2, 'c': 3}
>>> d['d'] = 4
>>> d
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>> s = {'a', 'b', 'c'}
>>> s
{'b', 'a', 'c'}
>>> s.add('d')
>>> s
{'d', 'b', 'a', 'c'}

这种差异的依据是什么?导致Python团队更改dict实现的效率提高是否也不适用于集合?

我不是在寻找指向有序集合实现的指针,也不是在寻找将dicts作为集合的替代品的方法。我只是想知道为什么Python团队没有使内置集合保留命令的同时保留命令。


1
这回答了你的问题了吗?Python是否有序集?
Mihai Chelaru

1
不,我了解Python没有内置的有序集合。我只是想知道为什么会这样,因为dict现在是有序的。
巴特·罗宾逊

4
使用模式不同,因此针对不同用例进行了优化。一个普遍的误解是,在CPython中,集合只是具有空值的字典,这是完全错误的:实现是不同的。如果您的问题没有解决,我可以发布详细的答案。
维姆

1
“使用模式是不同的,因此它们针对不同的用例进行了优化。” 我认为,对此问题的一个很好的答案将在此进行阐述。问题是什么使两种不同的方法对于相应的用例最佳。
Karl Knechtel

需要注意的是PyPy不使用相同的排序两个dictset自2.7。
MisterMiyagi

Answers:


10

集合和字典针对不同用例进行了优化。集合的主要用途是快速的成员资格测试,该测试与订单无关。对于命令而言,查找成本是最关键的操作,并且更可能出现密钥。对于集合,元素的存在或不存在是事先未知的,因此集合实现需要针对发现和未发现的情况进行优化。同样,对通用集合操作(​​例如联合和相交)的一些优化使得在不降低性能的情况下很难保留集合顺序。

虽然这两种数据结构都是基于散列的,但这是一个普遍的误解,即集合只是作为具有空值的字典来实现的。甚至在CPython 3.6中的紧凑dict实现之前,set和dict实现就已经存在很大差异,几乎没有代码重用。例如,字典使用随机探测,而集合使用线性探测和开放式寻址的组合来改善缓存局部性。初始线性探针(CPython中的默认9个步骤)将检查一系列相邻的键/哈希对,从而通过降低哈希冲突处理的成本来提高性能-连续的内存访问比分散的探针便宜。

这将是可能在理论上改变的CPython的一套实施类似于紧凑快译通,但在实践中也有缺点,和显着的核心开发者反对做出这样的改变。

集保持无序。(为什么?使用方式不同。另外,实现方式也不同。)

Guido van Rossum

集合使用的另一种算法不太适合保留插入顺序。如果需要订购,按套设置操作会失去灵活性和优化性。集合数学是根据无序集合定义的。简而言之,设置顺序不是在不久的将来。

雷蒙德·海廷格Raymond Hettinger)

可以在python-dev邮件列表中找到有关是否为3.7压缩集合的详细讨论以及为何反对使用它的答案。

总而言之,主要要点是用法模式不同(插入排序命令如** kwargs是有用的,对于集合则较少),压缩集的空间节省意义不大(因为只有键和哈希数组可用于与键,散列和值相反,它进行了稠密化),并且上述线性探查优化集中于紧凑的实现方式。

我将在下面复制雷蒙德的帖子,其中涵盖了最重要的观点。

2016年9月14日下午3:50,埃里克·斯诺(Eric Snow)写道:

然后,我将对集合进行相同的操作。

除非我有误解,否则雷蒙德反对对设置进行类似的更改。

那就对了。在人们开始疯狂之前,这里有一些关于这个问题的想法。

  • 对于紧凑型dict,空间节省是一项净赢,索引所消耗的额外空间以及键/值/哈希数组的超额分配被键/值/哈希数组的密度提高所抵消。但是,对于集合而言,网络不利得多,因为我们仍然需要索引和超额分配,但是只能通过密集化三个阵列中的两个来抵消空间成本。换句话说,当浪费键,值和哈希的空间时,压缩更为有意义。如果您输掉这三者中的一者,它就不再引人注目。

  • 集的使用模式与字典不同。前者有更多的命中或未命中查询。后者倾向于缺少较少的键查找。此外,对设置操作的一些优化使得很难在不影响性能的情况下保留设置顺序。

  • 我寻求替代途径来改善布景表现。我没有进行压缩(这不会节省太多空间,并且不会招致额外的间接费用),而是添加了线性探测以减少冲突的成本并提高缓存性能。这种改进与我提倡的字典压缩方法不兼容。

  • 目前,字典的排序副作用尚无保证,因此现在过早地坚持认为集合也已排序还为时过早。该文档已经链接到用于创建OrderedSet的配方( https://code.activestate.com/recipes/576694/),但似乎吸收率几乎为零。同样,既然Eric Snow给了我们快速的OrderedDict,从MutableSet和OrderedDict构建一个OrderedSet变得比以往任何时候都容易,但是我再也没有观察到任何真正的兴趣,因为典型的按组设置数据分析实际上并没有需要或关心订购。同样,快速成员资格测试的主要用途是与订单无关。

  • 就是说,我确实认为有空间向PyPI添加替代集合实现。特别是,对于可订购数据有一些有趣的特殊情况,其中可以通过比较整个键范围来加快设置操作(请参阅 https://code.activestate.com/recipes/230113-implementation-of- 为起点设置使用排序列表)。IIRC,PyPI已经具有用于类似集合的布隆过滤器和布谷鸟哈希的代码。

  • 我知道,将一大段代码接受到Python内核中是令人兴奋的,但是除非我们确定有必要,否则不要向进行其他数据类型的更大型重写的闸门敞开大门。

–雷蒙德·海廷格(Raymond Hettinger)

[Python-Dev]起,Python 3.6 dict变得紧凑并获得了私有版本;并于2016年9月对关键字进行了排序


2

讨论区

您的问题很贴切,不久前已经在python-devs上进行了大量讨论。R. Hettinger 在该主题中分享了一系列原理。在T. Peters 做出详细答复后不久,问题的状态现在似乎是无限的。

简而言之,保留插入顺序的现代命令的实现是唯一的,因此不适合使用集合。特别是,在所有地方都使用字典来运行Python(例如,__dict__在对象的名称空间中)。现代dict背后的主要动机是减小大小,使Python整体上具有更高的内存效率。相比之下,集合不如Python核心中的dict流行,因此劝阻这种重构。另请参见R. Hettinger 关于现代dict实现的演讲


观点

Python中集合的无序性质与数学集合的行为相似。不能保证订单。

相应的数学概念是无序的,因此强加诸如阶之类的方法很奇怪-R. Hettinger

如果在Python中将任何类型的顺序引入集合,则此行为将符合完全独立的数学结构,即有序集合(或Oset)。奥特斯在数学中扮演着独立的角色,尤其是在组合数学中。在钟的更换中观察到了奥赛特的一种实际应用。

具有无序集与一种非常通用且无处不在的数据结构相一致,该数据结构解开了大多数现代数学,即“ 集论”。我提交了,Python中的无序集很好。

另请参阅有关该主题的相关文章:

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.