2

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

>>> 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

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

4

6 回答 6

7

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

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

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

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

于 2013-01-25T18:23:43.003 回答
5

你应该使用的是:

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,没有变化

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

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

于 2013-01-25T18:32:22.140 回答
4

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

这是如何完成的:

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]
于 2013-01-25T18:20:33.990 回答
2

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

对于你正在做的事情,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
于 2013-01-25T18:25:38.367 回答
0

正如@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]
于 2013-01-25T18:27:19.180 回答
0
t = [e for e in t if e != 0.0]
于 2013-01-25T18:23:52.567 回答