【问题标题】:Removing from a list while iterating over it在迭代列表时从列表中删除
【发布时间】:2011-09-23 23:02:17
【问题描述】:

以下代码:

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 早期版本的机制是否发生了变化?

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

【问题讨论】:

  • 如果你只想要非奇数索引,你可以只做list[::2],同样l[1::2] 非偶数。几乎所有您想在迭代列表时从列表中删除的操作,您的设计都是错误的。

标签: python iterator


【解决方案1】:

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

[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 引用的文本自version 1.4 以来一直在教程中!)

【讨论】:

    【解决方案2】:

    正如 Mikola 解释的那样,您观察到的实际结果是由于从列表中删除一个条目会使整个列表移动一个位置,从而导致您错过元素。

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

    1. Dict 内部复杂,而列表则不然。列表基本上只是数组。 dict 必须在迭代时检测其何时被修改,以避免当 dict 的内部结构发生变化时崩溃。列表可以在不进行检查的情况下逃脱,因为它只是确保其当前索引仍在范围内。

    2. 从历史上看,(我现在不确定)python 列表是通过使用 [] 运算符进行迭代的。 Python 将评估 list[0]、list[1]、list[2] 直到它得到一个 IndexError。在这种情况下,python 在开始之前不会跟踪列表的大小,因此它无法检测到列表的大小已更改。

    【讨论】:

    • 对2的好答案。我认为您的第二个答案仍然有效,至少judging from this
    【解决方案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,依此类推。

    【讨论】:

    • 如果我正确阅读它并没有说它是未定义的(只是它是不可取的)。也许它已经改变了。
    【解决方案4】:

    在您的第一次迭代中,您并没有删除,而且一切都是花花公子。

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

    您要移除东西的事实意味着每次执行移除时都会跳过一个位置。

    【讨论】:

      猜你喜欢
      • 2011-03-18
      • 2018-09-26
      • 2020-05-27
      • 2021-03-04
      • 1970-01-01
      • 1970-01-01
      • 2014-10-26
      相关资源
      最近更新 更多