有这么一段代码。理论上,我希望 print 会输出每个元素,但它只输出 1、3、5
a = [1, 2, 3, 4, 5, 6]
for i in a:
print(i)
if i % 2 != 0:
a.remove(i)
# 1
# 3
# 5
有这么一段代码。理论上,我希望 print 会输出每个元素,但它只输出 1、3、5
a = [1, 2, 3, 4, 5, 6]
for i in a:
print(i)
if i % 2 != 0:
a.remove(i)
# 1
# 3
# 5
请记住,在遍历列表的同时修改列表是一种非常糟糕的做法,因为它充满了许多副作用。
在这种情况下发生了什么?
在第一次迭代中: ,因此该元素已从列表中
i = 1, i % 2 != 0 -> True删除。1结果,列表会发生变化并且会有所不同:
[2, 3, 4, 5, 6],进一步,为了进一步推理,您需要了解列表是如何迭代的。简而言之:在每次迭代中,选择index 中的下一个元素(魔术方法称为
__next__)。在第一次迭代中,带有 index 的元素
0分别在第二次迭代中返回 - 带有 index1等。但是在您的示例中,有一个细微差别,您在迭代它的过程中更改了原始列表
1,因此将在具有已更改列表索引的元素上执行转换[2, 3, 4, 5, 6]- 也就是说3,结果是值为 skipped 的元素2,因为迭代器对原始列表已被修改一无所知,只是将指针前进到下一个索引。视觉演示
如何解决问题没有副作用?
要按条件删除(过滤)元素,您可以使用,例如,列表包含:
或者内置函数
filter():结论:
但这会是一个新列表吗?
是的,链接会改变,但可以使用旧链接保存更新后的列表:
以相反的顺序进行:
最好不要每次都重建列表,而是“收缩”它并在最后更改一次长度
分析
让我们重写代码,替换
for为while.j- 遍历列表中的索引。改进了打印以显示j列表:当条件为真时,列表
a被缩短并且计数器j被递增。跳过的元素:复制原始列表
问题在于进行循环的列表发生变化。编辑原始循环。请参阅
a[:]循环的标题?。现在对列表的副本进行迭代,所有的麻烦都消失了:精确的计数器控制
while选项c
while也可以改进。它没有复制:虽然一切正常,但我不会那样做。在长列表上工作缓慢(O-big 术语的二次复杂性)。而且最后的代码也很复杂。
经典的
只要列表中的下一个值是奇数,索引
j就会落后于循环中的迭代。for在这种情况下,列表在迭代过程中也会发生变化,但它的长度和“尾巴”不会改变。这已经足够了,不会有任何意外。由于在迭代过程中我们不改变列表的长度,所以最后它必须被修剪(
del):Python 中的经典
Python 有另一种方法可以在不使用额外内存的情况下“就地”过滤列表:
事实上,这种方法重复了经典方法(与
del)。如果它最终成为最快的 - 更少的代码,更快的解释器,我不会感到惊讶。