python的“设置”稳定吗?

时间:2010-09-28 12:00:39

标签: python set

回答另一个SO问题时出现了问题(there)。

当我在python集上迭代几次(在调用之间没有更改它)时,我可以假设它总是以相同的顺序返回元素吗?如果没有,改变订单的理由是什么?它是确定性的还是随机的?或实施定义?

当我反复调用相同的python程序(不是随机的,不依赖于输入)时,我会得到相同的排序吗?

基本问题是,如果python set迭代顺序仅取决于用于实现集合的算法,还取决于执行上下文?

7 个答案:

答案 0 :(得分:13)

对于集合(或dicts,就此而言)的稳定性没有正式保证。但是,在CPython实现中,只要没有任何更改集合,项目将以相同的顺序生成。集合实现为开放式寻址哈希表(带有主要探测器),因此插入或删除项目可以完全改变顺序(特别是,当触发调整大小时,它会重新组织项目在内存中的布局方式。)您还可以有两个相同的集合,但仍以不同的顺序生成项目,例如:

>>> s1 = {-1, -2}
>>> s2 = {-2, -1}
>>> s1 == s2
True
>>> list(s1), list(s2)
([-1, -2], [-2, -1])

除非你非常确定你拥有相同的设置,并且在两次迭代之间没有触及它,所以最好不要依赖它保持不变。对你说的中间函数进行看似无关紧要的更改可能会产生非常难以发现的错误。

答案 1 :(得分:3)

  

当我打电话给同一个蟒蛇时   程序反复(不是随机的,不是   输入依赖),我会得到相同的   订购套装?

我可以在快速实验后回答这部分问题。使用以下代码:

class Foo(object) :
  def __init__(self,val) :
    self.val = val
  def __repr__(self) :
    return str(self.val)

x = set()
for y in range(500) :
  x.add(Foo(y))
print list(x)[-10:]

我可以在另一个问题中触发我所询问的行为。如果我反复运行,那么输出会改变,但不是每次运行都会改变。它似乎是“弱随机”,因为它变化缓慢。这肯定是依赖于实现的所以我应该说我在雪豹上运行macports Python2.6。虽然程序会在很长一段时间内输出相同的答案,但做一些影响系统熵池的事情(写入磁盘大部分都有效)会让某些时候将它踢进不同的输出。

类Foo只是一个简单的int包装器,因为实验表明这不会发生在一组int中。我认为这个问题是由于对象缺少__eq____hash__成员引起的,尽管我非常想知道隐含它的基本解释/方法。同样有用的是重现/重复“坏”运行的某种方式。有谁知道它使用的种子,或者我如何设置种子?

答案 2 :(得分:2)

setfrozenset本质上是无序集合。在内部,集合基于hash table,并且键的顺序取决于插入顺序和hash算法。在CPython(又称标准Python)中,小于机器字大小(32位或64位)的整数会对其自身进行哈希处理,但是文本字符串,bytes字符串和datetime对象会哈希为随机变化的整数;您可以通过设置PYTHONHASHSEED环境变量来控制它。

来自__hash__文档:

  

注意

     

默认情况下,__hash__()strbytes的{​​{1}}值   使用无法预测的随机值对对象“加盐”。虽然他们   在单个Python进程中保持不变,但不是   在重复调用Python之间是可预测的。

     

这旨在提供防止拒绝服务的保护   由利用最坏情况的精心选择的输入引起的   字典插入的性能,O(n ^ 2)复杂度。看到   http://www.ocert.org/advisories/ocert-2011-003.html了解详情。

     

更改哈希值会影响dict,set和的迭代顺序   其他映射。 Python从未对此顺序做任何保证   (通常在32位和64位版本之间变化)。

     

另请参阅PYTHONHASHSEED。

对其他类进行哈希处理的结果取决于该类的__hash__方法的详细信息。

所有这些的结果是,您可以拥有两个包含相同字符串的集合,但是将它们转换为列表时,它们可以比较不相等。或者他们可能不会。 ;)这是一些演示此代码的代码。在某些运行中,它将循环播放,不打印任何内容,但是在其他运行中,它将迅速找到使用与原始顺序不同的集合。

datetime

典型输出

from random import seed, shuffle

seed(42)

data = list('abcdefgh')
a = frozenset(data)
la = list(a)
print(''.join(la), a)

while True:
    shuffle(data)
    lb = list(frozenset(data))
    if lb != la:
        print(''.join(data), ''.join(lb))
        break    

答案 3 :(得分:1)

集合的定义是无序的,唯一的元素("Unordered collections of unique elements")。您应该只关心接口,而不是实现。如果你想要一个有序的枚举,你应该将它放入一个列表并对其进行排序。

Python有许多不同的实现。不要依赖于未记录的行为,因为您的代码可能会破坏不同的Python实现。

答案 4 :(得分:1)

这绝对是实现定义的。 specification of a set仅表示

  

作为无序集合,集合不记录元素位置或插入顺序。

为什么不使用OrderedDict创建自己的OrderedSet类?

答案 5 :(得分:1)

正如所指出的,这完全是一个实现细节。

只要你没有改变调用之间的结构,就应该有无理由的只读操作(=迭代)随时间变化:没有理智的实现。甚至可用于实现集合的随机化(=非确定性)数据结构(例如跳过列表)也不会在没有发生变化时改变阅读顺序。

因此,理性,你可以放心地依赖这种行为。

(我知道某些GC可能会在后台线程中重新排序内存,但即使这种重新排序在数据结构层面上也不会引人注意,除非发生错误。)

答案 6 :(得分:1)

答案只是一个否。

Python设置操作不稳定。

我做了一个简单的实验来证明这一点。

代码:

import random
random.seed(1)

x=[]
class aaa(object):
    def __init__(self,a,b):
        self.a=a
        self.b=b

for i in range(5):
    x.append(aaa(random.choice('asf'),random.randint(1,4000)))

for j in x:
    print(j.a,j.b)

print('====')
for j in set(x):
    print(j.a,j.b)

运行两次,您将得到:

第一次结果:

a 2332
a 1045
a 2030
s 1935
f 1555
====
a 2030
a 2332
f 1555
a 1045
s 1935

Process finished with exit code 0

第二次结果:

a 2332
a 1045
a 2030
s 1935
f 1555
====
s 1935
a 2332
a 1045
f 1555
a 2030

Process finished with exit code 0

this answer中的注释中解释了原因。

但是,有一些方法可以使其稳定: