在迭代列表时从列表中删除

问题描述 投票:0回答:5

以下代码:

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]

  1. 为什么会输出这些特定值?
  2. 为什么没有给出错误表明底层迭代器正在被修改?
  3. 与早期版本的 Python 相比,这种行为的机制是否发生了变化?

请注意,我并不是要解决这种行为,而是要理解它。

python iterator
5个回答
24
投票

我为回答这个问题争论了一段时间,因为类似的问题已经在这里被问过很多次了。但它的独特性足以让人们相信它是无罪的。 (不过,如果其他人投票结束,我不会反对。)这是对正在发生的事情的直观解释。

[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 变慢很多,尤其是在速度真正重要的地方——循环。

与早期版本的 Python 相比,这种行为的机制是否发生了变化?

简而言之,不。或者至少我
高度

怀疑它,而且自从我学习Python(2.4)以来它确实是这样的。坦率地说,我希望可变序列的任何直接实现都以这种方式表现。哪位知道的比较清楚的请指正。 (实际上,快速文档查找确认了 Mikola 引用的文本自 1.4 版以来一直存在于教程中!)


5
投票

但在我看来,更有趣的问题是为什么 python 在发生这种情况时不选择生成错误消息。如果您尝试修改字典,它确实会产生这样的错误消息。我认为有两个原因。

    字典内部很复杂,而列表则不然。列表基本上就是数组。字典在迭代时必须检测其何时被修改,以避免当字典的内部结构发生变化时崩溃。列表可以在不进行该检查的情况下逃脱,因为它只是确保其当前索引仍在范围内。
  1. 历史上(我现在不确定),Python 列表是通过使用 [] 运算符进行迭代的。 Python 会计算 list[0]、list[1]、list[2],直到出现 IndexError。在这种情况下,Python 在开始之前不会跟踪列表的大小,因此它无法检测列表的大小是否已更改。

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,依此类推。


1
投票

因此代码:

a = list(range(10)) remove = True for b in reversed(a): if remove: a.remove(b) remove = not remove print(a)

产生预期的结果:
[0, 2, 4, 6, 8]

    


0
投票

第二次迭代,您位于序列的位置 [1],并且删除了“1”。然后迭代器将您带到序列中的位置 [2],现在是“3”,因此“2”会被跳过(因为“2”由于被删除而现在位于位置 [1])。当然,“3”不会被删除,因此您继续到序列中的位置 [3],现在是“4”。它被删除,带你到位置 [5],现在是“6”,依此类推。

您要删除内容的事实意味着每次执行删除时都会跳过一个位置。

© www.soinside.com 2019 - 2024. All rights reserved.