【问题标题】:Can anyone explain this bizarre bug iterating over a set? [closed]谁能解释这个迭代集合的奇怪错误? [关闭]
【发布时间】:2018-01-11 09:10:17
【问题描述】:

我有一个for thing in a_set: 形式的循环。它工作不正确,因为它偶尔会不一致地从集合中拉出相同的东西两次。 (这不会导致程序崩溃。它只是得到错误的答案。)我无法确定任何关于错误行为的确定性;但我调试它的尝试清楚地表明,有时会发生奇怪的事情。在我最仔细观察的情况下,集合中有 3 个项目(之前和之后),循环执行了 4 次,其中一个项目重复了一次。这些项目是对我创建的类的对象的引用(更像是 C 结构)。当我将 for 语句更改为 for thing in list(a_set): 时,不良行为消失了。

我完全无法解释错误的行为。我非常确定循环体中的任何内容都不会导致它正在执行的操作发生两次或更改事物变量的值。我相当肯定循环中发生的事情不会试图影响集合的组成。此外,即使可以,我相信这会导致RuntimeError。对于可能导致这种情况的假设,我完全不知所措。连续运行相同代码缺乏可重复性尤其神秘。我在更简单的场景中重新创建症状的尝试失败了。尽管如此,我会觉得将list() 调用留在那里只是为了解决我无法解释的问题很愚蠢。任何其他人的假设都会受到欢迎。我需要了解在调试时应该尝试消除哪些类型的事情。

更新:我认为这个问题被错误地搁置了,因为它声称它是题外话。在这种情况下,缺乏可重复性是问题所在,我怀疑我缺少的语言有一些细微差别。事实上,事实证明确实如此,MSeifert 的回答让我了解了导致它的原因。然而,正如我在对他的回答的评论中指出的那样,这并不像他推测的那么简单。

我还说集合中的对象是可变的,从而混淆了这个问题。他们不是。它们是对属性可变的对象的引用。 (这可以从我写的内容中推断出来,但是我在一般意义上而不是在 Python 技术意义上错误地使用了“可变”这个词。)散列的是对象的地址,与它的值无关属性。如果这些对象引用是可变的,Python 一开始就不会让我把它们放在一个集合中。

【问题讨论】:

  • 你需要一些方法来重现这个。你的集合是否有你正在改变的可变对象?
  • @matt。问题在于缺乏可重复性。是的,集合中的对象是可变的。它们具有正在改变的属性;但它们仍然是相同的对象。在大多数没有发生不良行为的情况下(没有列表),它们的属性也会发生这种变化。
  • 你是如何创建你的集合的?
  • @matt。我从一个空集开始,并使用 add 函数一次添加一个项目。
  • "是的,集合中的对象是可变的。它们具有正在更改的属性" - 集合元素不应该是可变的,或者至少不应该以影响== 和@987654326 的方式可变@。如果您没有立即意识到这将是一个问题,那么您应该完全避免将可变对象放入集合中。

标签: python python-2.7 set iteration


【解决方案1】:

如果在您添加 list(a_set) 时错误消失了,那么您很可能在迭代期间更改了集合。一般来说,这会引发RuntimeError,但如果您添加的元素与删除的元素一样多,它不会触发:

a = {1,2,3}
for item in a:
    print(item)
    a.add(item+3)  # add one item
    a.remove(item) # remove one item

打印数字131(数量实际上是一个实现细节,因此您可能会看到不同的数量)并且在循环之前和之后以及在每次迭代开始时set 包含@987654327 @元素。

但是,如果我添加 list 调用,它会创建原始集合的副本(作为列表),并且仅迭代原始集合中存在的元素:

a = {1,2,3}
for item in list(a):
    print(item)
    a.add(item+3)
    a.remove(item)

print(a)

打印:

1
2
3
set([4, 5, 6])   # totally changed!

在 cmets 中,您注意到集合中的类是可变的,因此即使您可能认为删除并添加相同的元素,它也可能不再是相同的元素(来自set 的观点)。一般来说,您不应该将可变类放在set 中或作为dict 中的键,因为您必须非常小心,可变性不会影响__hash____eq__ 方法的结果。

只是一个迭代看似“随机”数量的集合元素的示例:

class Fun(object):
    def __init__(self, value):
        self.value = value

    def __repr__(self):
        return '{self.__class__.__name__}({self.value})'.format(self=self)

    def __eq__(self, other):
        return self.value == other.value

a = {Fun(1),Fun(2),Fun(3)}
for item in a:
    print(item)
    a.add(Fun(item.value+3))
    a.remove(item)

实际上会显示一个“随机”(不是真正随机的,它仅取决于实例的哈希值,在这种情况下,哈希取决于每次运行代码时都会更改的类对象的 idFun 每次运行 sn-p 的对象。

【讨论】:

  • 您的回答让我知道出了什么问题。这是在一个递归回溯程序中。事实证明,该集合正在循环中被修改;但是,在循环的下一次迭代之前,回溯过程总是将集合的内容放回到从一开始就存在的同一组对象中。即,虽然它正在移除一个对象,但它正在放回完全相同的对象。在我看来,这个系列并没有改变。但是,删除/添加显然仍然在过多地处理集合,以使迭代器无法“正确”运行。非常感谢。
猜你喜欢
  • 2010-10-19
  • 2013-01-10
  • 2013-09-24
  • 2014-09-16
  • 1970-01-01
  • 1970-01-01
  • 2015-02-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多