【问题标题】:The specific behavior of the iteration faux pas迭代失礼的具体行为
【发布时间】:2013-01-25 18:19:34
【问题描述】:

最近有人问我关于在迭代列表时更改列表的错误。他们提出了以下场景(我现在已经更新了一个更好的例子)作为可能的用例,当行为可能是可取的:

>>> jersey_numbers = [4, 2, 3, 5, 1]  # list of places in a race
>>> for jersey_number in jersey_numbers:
        if jersey_number == 2:  # disqualify jersey number 2 for a false start
            t.remove(jersey_number)
>>> t
[4, 3, 5, 1]  # jersey number 3 is now in second place

这种行为是否足够规律,可以在这样的用例中使用?

【问题讨论】:

标签: python


【解决方案1】:

当您从列表中删除一个项目时,列表中的所有内容都会移动...

[1, 2, 3, 4, 5]
#remove   ^
[1, 2, 3, 5]

如果您在迭代对象时执行此操作,并且如果您想要删除彼此相邻的项目,那么当您删除第一个项目时,第二个项目将转移到它的位置。 for 循环将继续递增列表中它从中提取值的位置,这会导致它跳过碰到的值以代替您删除的项目。

这是一个reference——我们都知道这是有据可查的行为:)

【讨论】:

  • 使用一点 ascii 可以做的事情真是太棒了 :-)
【解决方案2】:

你应该使用的是:

t = filter(None, t)  # or
t = [x for x in t if x]

或者如果你的情况实际上更复杂:

t = filter(lambda x: x != something, t)  # or
t = [x for x in t if x != something]

顺便说一句,remove 删除了第一个匹配元素,不一定是您的 x 当前指向的元素,尽管在您的简单案例中行为是等效的。

发生的情况是,当您迭代列表时,您删除了元素并且迭代器不知道它,假设您的列表是 [1,0,0,2]

  • 迭代器在1,没有变化,下一个
  • 迭代器在0,你删除了一些0,这是第一个,列表改变了大小,现在迭代器指向第二个零,下一个
  • 迭代器指向2,没有变化

实际上,您的第一个算法每隔一个零就删除一次。

你的第二个算法应该不起作用,如果你说它起作用,也许你没有足够的测试它。

【讨论】:

    【解决方案3】:

    错误是您在迭代列表时正在修改列表。这不符合您的预期(当您删除当前元素时,会跳过下一个元素)。

    这是如何做到的:

    In [18]: t = [5.0, 5.0, 5.0, 4.0, 0.0, 5.0, 0.0, 3.0, 5.0, 5.0, 0.0, 0.0, 4.0, 5.0, 0.0, 5.0, 4.0, 5.0, 3.0, 3.0, 5.0, 5.0, 5.0, 5.0]
    
    In [19]: t[:] = [val for val in t if val != 0]
    

    【讨论】:

    • 它是被允许的并且结果是确定的——只是对新人来说它是如何工作的并不是很明显。
    • @mgilson:不知道行为是否明确。你能提供一个参考吗?谢谢。
    • 为什么 t[:] = ... 而不是 t = ... ?
    • @PaulSeeb:不更改列表引用。可能在我们没有显示的代码中还有对t 的其他引用。由于我们不确定,所以我选择了更保守的方法。
    • @Wooble -- 使用切片分配将允许 OP 正确修改列表 -- 这个没有问题。
    【解决方案4】:

    您应该迭代列表的浅表副本,因为您正在修改列表,这可能会导致在迭代期间遗漏某些项目。

    对于你正在做的事情,filter() 是一个不错的选择。

    filter(lambda x:x != 0.0,t)
    

    使用浅拷贝:

    In [7]: t = [5.0, 5.0, 5.0, 4.0, 0.0, 5.0, 0.0, 3.0, 5.0, 5.0, 0.0, 0.0, 4.0, 5.0, 0.0, 5.0, 4.0, 5.0, 3.0, 3.0, 5.0, 5.0, 5.0, 5.0]
    
    In [8]: for x in t[:]:   # a shallow copy of t
        if x==0.0:
            t.remove(x)
    
    In [9]: 0.0 in t
    Out[9]: False
    

    【讨论】:

    • 如果您是一个因某种原因而被 Python 困住的 haskell 程序员,这是一个不错的选择吗? (我发现列表理解更加 Pythonic 并且更具可读性)
    【解决方案5】:
    t = [e for e in t if e != 0.0]
    

    【讨论】:

    • 我不是反对者,但您是否认为这并不能真正回答仅关于迭代时修改行为的问题。
    • 这个问题被编辑了11次,所以我的答案应该是以前的版本。
    【解决方案6】:

    正如@mgilson 所说,当您删除0.0 值时,第二个0.0 取而代之并被遗漏。

    您可以遍历浅表副本,并将其从原始副本中删除:

     >>> t = [1, 2, 3, 3, 3, 4, 5]
     >>> for value in t[:]:
             if value == 3:
                 t.remove(value)
     >>> t
     [1, 2, 4, 5]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-31
      • 1970-01-01
      • 1970-01-01
      • 2016-05-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多