以下代码:
a = list(range(10))
remove = False
for b in a:
if remove:
a.remove(b)
remove = not remove
print(a)
使用Python 3.2时输出[0, 2, 3, 5, 6, 8, 9]
,而不是[0, 2, 4, 6, 8]
。
请注意,我不是要解决这个问题,而是要了解它。
答案 0 :(得分:16)
我辩论了一段时间回答这个问题,因为这里曾多次提出类似的问题。但它的独特性足以让人怀疑。 (不过,如果其他人投票结束,我也不会反对。)这是对正在发生的事情的直观解释。
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] <- b = 0; remove? no
^
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] <- b = 1; remove? yes
^
[0, 2, 3, 4, 5, 6, 7, 8, 9] <- b = 3; remove? no
^
[0, 2, 3, 4, 5, 6, 7, 8, 9] <- b = 4; remove? yes
^
[0, 2, 3, 5, 6, 7, 8, 9] <- b = 6; remove? no
^
[0, 2, 3, 5, 6, 7, 8, 9] <- b = 7; remove? yes
^
[0, 2, 3, 5, 6, 8, 9] <- b = 9; remove? no
^
由于没有其他人,我会尝试回答您的其他问题:
为什么没有给出错误来指示底层迭代器正在被修改?
要抛出错误而不禁止许多完全有效的循环结构,Python必须知道 lot 有关正在发生的事情,并且它可能必须在运行时获取该信息。所有这些信息都需要时间来处理。它会使Python变得更慢,只是在速度真正重要的地方 - 循环。
对于这种行为,是否已从早期版本的Python更改了机制?
简而言之,没有。或者至少我高度怀疑它,当然,自从我学习Python(2.4)以来它一直表现得这样。坦率地说,我希望任何直接实现的可变序列都能以这种方式运行。谁知道更好,请纠正我。 (实际上,快速文档查找确认Mikola引用的文本自version 1.4以来一直在教程中!)
答案 1 :(得分:4)
正如Mikola解释的那样,您观察到的实际结果是由于从列表中删除条目会将整个列表移动一个位置导致您错过元素。
但是在我看来,更有趣的问题是为什么python在发生这种情况时不会选择产生错误消息。如果您尝试修改字典,它确实会产生这样的错误消息。我认为有两个原因。
Dict在内部是复杂的,而列表则不是。列表基本上只是数组。 dict必须在迭代时检测其修改时间,以避免在dict的内部结构发生变化时崩溃。列表可以在不进行检查的情况下离开,因为它只是确保其当前索引仍在范围内。
从历史上看,(我现在不确定),使用[]运算符迭代python列表。 Python会评估list [0],list [1],list [2],直到得到一个IndexError。在这种情况下,python在开始之前没有跟踪列表的大小,所以它没有检测到列表大小已经改变的方法。
答案 2 :(得分:3)
当然,在迭代它时修改数组是不安全的。该规范说这是一个坏主意,行为未定义:
http://docs.python.org/tutorial/controlflow.html#for-statements
那么,接下来的问题是这里究竟发生了什么?如果我不得不猜测,我会说它正在做这样的事情:
for(int i=0; i<len(array); ++i)
{
do_loop_body(i);
}
如果您认为这确实是正在发生的事情,那么它会完全解释观察到的行为。在当前指针处或之前删除元素时,将整个列表向左移动1。第一次,你删除1 - 像往常一样 - 但现在列表向后移动。下一次迭代而不是点击2,你点击3.然后你删除一个4,然后列表向后移动。下一次迭代7,依此类推。
答案 3 :(得分:0)
在你的第一次迭代中,你不会删除所有东西都是花花公子。
第二次迭代你处于序列的位置[1],然后你删除'1'。然后迭代器将您带到序列中的位置[2],现在为'3',因此'2'被跳过(因为删除,因为'2'现在位于位置[1])。当然'3'不会被删除,所以你继续在序列中定位[3],现在是'4'。这会被删除,带你到位置[5],现在是'6',依此类推。
您要删除的事实意味着每次执行删除时都会跳过某个位置。