迭代列表时删除列表
- 2024-12-11 08:47:00
- admin 原创
- 184
问题描述:
以下代码:
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]
为什么它会输出这些特定的值?
为什么没有给出错误来表明底层迭代器正在被修改?
相对于早期版本的 Python,这种行为的机制是否有所改变?
请注意,我并不是想解决这种行为,而是想理解它。
解决方案 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引用的文本自1.4 版以来就一直存在于教程中!)
解决方案 2:
正如 Mikola 所解释的,您观察到的实际结果是由于从列表中删除一个条目会导致整个列表移动一个位置,从而导致您错过元素。
但在我看来,更有趣的问题是,为什么 Python 在发生这种情况时不选择生成错误消息。如果您尝试修改字典,它确实会生成这样的错误消息。我认为这有两个原因。
字典内部很复杂,而列表则不然。列表基本上就是数组。字典必须在迭代过程中检测其修改时间,以避免在字典内部结构发生变化时崩溃。列表可以不进行该检查,因为它只是确保其当前索引仍在范围内。
从历史上看(我现在不确定),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,依此类推。
解决方案 4:
如果在 for 循环中添加 reversed(),则可以向后遍历数组,同时删除项目并获得预期的输出。数组中元素的位置取决于前面的元素,而不是后面的元素:
因此代码:
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]
解决方案 5:
在第一次迭代中,您无需删除,一切都很顺利。
第二次迭代时,您位于序列中的位置 [1],然后删除“1”。然后迭代器将您带到序列中的位置 [2],现在为“3”,因此跳过了“2”(因为删除后“2”现在位于位置 [1])。当然“3”不会被删除,因此您继续到序列中的位置 [3],现在为“4”。删除该位置后,您将转到位置 [5],现在为“6”,依此类推。
事实上,删除东西意味着每次执行删除操作时都会跳过一个位置。
扫码咨询,免费领取项目管理大礼包!