2013-01-22 23 views
1
a = [0,1,2,3,4,5] 
for b in a: 
    print ":"+str(b) 
    a.pop(0) 

认为这将工作以便通过整个列表及其所有项目我运行此代码,并期望这一点。Python for循环列表有趣的结果

:0 
0 
:1 
1 
:2 
2 
:3 
3 
:4 
4 
:5 
5 

相反,我得到这个:

:0 
0 
:2 
1 
:4 
2 

现在我明白为什么会这样,但是这是在Python错误?难道它不应该通过所有的原始对象而不是当前列表的长度?为什么这不会抛出错误? IE:它不应该仍然这样做:

:0 
0 
:1 
2 
:2 
4 
:3 
Error 
:4 
Error 
:5 
Error 

回答

4

您遍历一个列表,并在同一时间改变它。通过使用.pop()您缩短了列表,但迭代器指针未更新。

使用复制代替:

for b in list(a): 

for b in a[:]: 

其中[:]片记号返回一个列表副本。

另一种方法是使用while循环,而不是:

while a: 
    print a.pop(0) 

因为空单测试作为布尔False

python for循环使用它的参数作为迭代器,它本身不保存索引。 for循环无法“知道”已删除的元素。相反,它是list()迭代器,它可将指针:

>>> a = [0,1,2,3,4,5] 
>>> itera = iter(a) 
>>> itera.next() # index 0 -> a[0] is 0 
0 
>>> a.pop(0) 
0 
>>> a 
[1,2,3,4,5] 
>>> itera.next() # index 1 -> a[1] is 2 
2 

这个迭代保持一个计数器,每当你对迭代器调用next()时候它会给你下一个索引,无论该值可以在值为,直到计数器等于列表中的当前的长度。

+0

我已经明白了为什么,但不应该抛出一个错误? – gabeio

+0

@gabeDel:你为什么期望出现错误? –

+0

哦,我忘记了名单正在缩短,但它仍然是由列表的长度,而不是每个元素。 – gabeio

5

这是完全“预期”和记录的行为。当你遍历列表时,你基本上遍历内存位置。当你从列表中弹出一些东西时,列表中的所有内容都会将1索引移动到靠近列表开头的位置。因此,你最终会跳过项目。到达列表末尾时迭代停止。

通常做这样的事情的时候,你要遍历列表的副本:

for b in a[:]: 
    ... 

正如评论所指出的,如果你遍历以相反的顺序列表:

for b in reversed(a): 
    a.pop() 

这可以按照预期工作,因为您不断拉扯最终元素,因此您不会将位置移到您尚未看到的任何元素的列表中。

+0

在某些情况下另一种有用的方法是以相反的顺序进行迭代:'for b in reverse(a)' – kindall

+0

@kindall - 感谢您的评论。我已将这些信息纳入我的答案。 – mgilson

+0

-1是'.pop()'的默认参数,因此您可以安全地忽略它。 –

0

如果你想在一个循环中使用.pop(),一个常见的成语是与while使用它:

a = [0,1,2,3,4,5] 
while a: 
    print ":{}".format(a.pop(0)) 

或者,如果你想打印的图案,你必须有:

a = [0,1,2,3,4,5] 
while a: 
    print ":{}\n{}".format(a[0],a.pop(0)) 

打印

:0 
0 
:1 
1 
:2 
2 
:3 
3 
:4 
4 
:5 
5 
0

在的每次迭代中0环,我们必须检查车况b in a所以,当你开始:

a = [0,1,2,3,4,5] 
for b in a: 
    print ":"+str(b) 
    a.pop(0) 

b = 0in a(在意义的元素)是5。现在,您打印字符串版本a[b],然后删除数组的第一个元素。因此迭代二变成了:

a = [1, 2, 3, 4, 5] 
b = 1 (it incremented) 
size of a = 4 (it shrank) 

接下来他们将b=2,的尺寸变成3.这将产生出界你期待的错误,因为b将不会再发生的最后一次迭代大于数组的大小,所以我们完成了。