当我在迭代列表时修改它,为什么Python会跳过元素?

17

我目前在开发Python程序,我刚刚注意到语言中的foreach循环或者列表结构有些问题。我将提供一个一般性示例来简化我的问题,因为我在我的程序和通用示例中都得到了相同的错误行为:

x = [1,2,2,2,2]

for i in x:
    x.remove(i)

print x        

这里的问题很简单,我认为这段代码应该可以从列表中删除所有元素。问题在于,在执行后,列表中始终保留2个剩余元素。

我做错了什么?提前感谢所有的帮助。

编辑:我不想清空列表,这只是一个例子...


你遇到的问题有以下解决方案,但我不禁想到它应该缩短为“x = []”。如果你发布导致问题的真实代码,可能会更有趣? - gnud
你应该发布你真正的问题。list.remove非常低效,因为它必须搜索列表,然后将所有跟在被删除元素之后的元素向前移动。 - Miles
8个回答

38

在Python中,不建议修改正在迭代处理的列表,这是一个经过充分文档化的行为。请尝试使用以下代码:

for i in x[:]:
    x.remove(i)
< p >[:]返回一个"slice",其中包含所有x的元素,因此实际上是x的一个副本。


这并没有解释为什么在rogeriopvl的问题中列表包含两个元素。如果这个解释是正确的,那么列表应该是完整的。 - Boris Gorelik
@bgbg:“这不安全”完美地解释了这种行为。像那样模糊的规范是委婉地说“未定义”。 - SingleNegationElimination
对于这个问题,最优雅的解决方案加1分。反向迭代似乎更加混乱。 - willwest
3
当你需要复制一个列表时,list(x)比x[:]更符合Python风格。 - MortenB

10

当您删除一个元素并且for循环增加到下一个索引时,然后会跳过一个元素。

反向执行。或者请说明您真正的问题。


6

我认为,总的来说,当你写:

for x in lst:
    # loop body goes here

在幕后,Python 做的事情类似于这样:
i = 0
while i < len(lst):
    x = lst[i]
    # loop body goes here
    i += 1

如果你在循环体中插入lst.remove(x),那么或许你就能够看到为什么会得到你目前的结果了?
实际上,Python使用一个移动指针来遍历列表。指针开始时指向第一个元素。然后你移除了第一个元素,这样第二个元素就变成了新的第一个元素。然后指针移动到新的第二个元素 - 之前是第三个元素。以此类推。(如果你使用[1,2,3,4,5]而不是[1,2,2,2,2]作为样本列表,可能会更清晰明了)

3

我知道这是一个旧帖子,已经有了一个被接受的答案,但对于那些可能仍在寻求答案的人...

一些之前的答案表明,在迭代过程中更改可迭代对象是一个不好的想法。但作为突出显示正在发生的事情的一种方式...

>>> x=[1,2,3,4,5]
>>> for i in x:
...     print i, x.index(i)
...     x.remove(i)
...     print x
...
1 0
[2, 3, 4, 5]
3 1
[2, 4, 5]
5 2
[2, 4]

希望这个图示能更好地解释。

3
为什么不直接使用以下方法:
x = []

可能是因为你正在遍历的数组被修改了。

如果你想以你的方式清空数组,请尝试Chris-Jester Young的答案。


1

关于中断条件,我同意John Fouhy的看法。复制列表并遍历可以用于remove()方法,正如Chris Jester-Young所建议的那样。但如果需要弹出特定项,则像Erik提到的那样反向迭代是起作用的,此时可以原地操作。例如:

def r_enumerate(iterable):
    """enumerator for reverse iteration of an iterable"""
    enum = enumerate(reversed(iterable))
    last = len(iterable)-1
    return ((last - i, x) for i,x in enum)

x = [1,2,3,4,5]
y = []
for i,v in r_enumerate(x):
    if v != 3:
        y.append(x.pop(i))
    print 'i=%d, v=%d, x=%s, y=%s' %(i,v,x,y)


或者使用xrange:

x = [1,2,3,4,5]
y = []
for i in xrange(len(x)-1,-1,-1):
    if x[i] != 3:
        y.append(x.pop(i))
    print 'i=%d, x=%s, y=%s' %(i,x,y)

0
如果你需要从一个列表中过滤出一些内容,使用列表推导式可能是一个更好的选择:
newlist = [x for x in oldlist if x%2]

例如,可以从整数列表中过滤出所有偶数。


0

计算机内存中存储的列表。这涉及到指向内存对象的指针。当你在一个逐个元素的循环中移除一个元素时,你会将指针移动到内存地址中下一个可用的元素。

你正在修改内存并遍历其中的内容。 元素的指针在列表中移动到下一个可用位置。 所以,在大小为5的情况下...输入代码在这里

 [**0**,1,2,3,4]
remove 0   --->  [1,**2**,3,4]  pointer moves to second index.
remove 2   --->  [1,3,**4**] pointer moves to 3rd index.
remove 4   --->  [1,3]

当我的学生使用pop(1)时,我正在向他们解释这个问题。这是另一个非常有趣的副作用错误。

x=[1,**2**,3,4,5]
for i in x:
  x.pop(1)
  print(x,i)

[1, **3**, 4, 5] 1   at index 0 it removed the index 1 (2)
[1, **4**, 5] 3      at index 1 it removed the index 1 (3)
[1, 5] 5         at index 2 it removed the index 1 (4)

哈哈。 他们说为什么这不工作……我的意思是……它确实做了你告诉它要做的事情。它又不是读心术。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接