在迭代时从列表中移除
以下代码:
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, 3, 5, 6, 8, 9]
0,2,3,5,6,8,9 [0, 2, 3, 5, 6, 8, 9]
,而不是[0, 2, 4, 6, 8]
。
- 为什么它输出这些特定的值?
- 为什么没有错误指出底层迭代器正在被修改?
- 从这个行为的angular度来看,Python的早期版本有没有改变?
请注意,我并不是想要解决这个问题,而是去了解它。
我辩论过一段时间,因为类似的问题在这里被多次提及。 但它只是独特的,以获得怀疑的好处。 (不过,如果其他人投票结束,我不会反对)。下面是对发生事件的视觉解释。
[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将不得不知道发生了什么,并且可能必须在运行时获得这些信息。 所有这些信息都需要时间来处理。 这会让Python变得更慢,只是在速度真正重要的地方 – 一个循环。
从这个行为的angular度来看,Python的早期版本有没有改变?
总之,没有。 或者至less我非常怀疑它,自从我学习了Python(2.4)后,它肯定performance得如此。 坦率地说,我期望任何一个可变序列的简单实现就像这样。 任何人都知道更好,请纠正我。 (实际上,快速文档查询确认了Mikola引用的文本从1.4版本开始就已经在教程中了!)
正如米科拉所解释的那样,你观察到的实际结果是由于从列表中删除一个条目而将整个列表转移一个点而导致你错过了元素。
但是更为有趣的问题是,为什么python在发生这种情况时不会select产生错误消息。 如果您尝试修改字典,它会产生这样的错误信息。 我认为有两个原因。
-
字典在内部是复杂的,而列表不是。 列表基本上只是数组。 字典必须检测它的修改时间是否被迭代,以避免当字典内部结构改变时崩溃。 一个列表可以离开而不做检查,因为它只是确保其当前索引仍然在范围内。
-
从历史上看(我现在还不确定),python列表通过使用[]运算符进行迭代。 Python将评估列表[0],列表[1],列表[2],直到它有一个IndexError。 在这种情况下,python在开始之前没有跟踪列表的大小,所以没有办法检测列表的大小是否已经改变。
当然,迭代时修改数组是不安全的。 规范说这是一个坏主意,行为是未定义的:
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,依此类推。
在你的第一个迭代中,你并没有移除,而是一切花哨。
第二次迭代,你在序列的位置[1],并删除'1'。 然后,迭代器将你带到序列中的位置[2],现在是'3',所以'2'被跳过(因为'2'现在在位置[1],因为移除了)。 当然,'3'不会被删除,所以你继续按照现在的'4'的顺序来定位[3]。 这被删除,带你到现在是'6'的位置,等等。
事实上,你正在删除的东西意味着一个位置会跳过每次执行删除。