For 循环的迭代次数少于我在 Python 中的预期

Posted

技术标签:

【中文标题】For 循环的迭代次数少于我在 Python 中的预期【英文标题】:For loop iterates less times than I expected in Python 【发布时间】:2019-10-17 05:40:27 【问题描述】:

我希望下面的循环迭代六次,而不是使用 python3 迭代三次。我不明白这种行为。 我知道当我删除元素时列表会发生变化,但我不知道这会如何影响 for 循环条件。 为什么循环迭代不到六次?

a = [1, 2, 3, 4, 5, 6]
for elem in a:
        del a[0]
        print(a)

【问题讨论】:

提示:列表迭代器提供第一个、第二个、第三个...项。 根据经验,如果您在迭代期间修改要迭代的集合,您将注定失败 【参考方案1】:

CPython 中的list 迭代器通过迭代列表的位置 工作。你可以认为它是这样工作的:

def list_iter(items: list):
    index = 0
    while True:
        yield items[index]
        index += 1

换句话说,迭代在 0 处提供项目,然后是 1,然后是 2,依此类推。没有预取项目 - 需要时从列表中查找项目。

当您在每个步骤中删除第一项时,列表在每个步骤中都会缩短 1。由于您从 6 个项目的列表开始,因此在第三次迭代中它减少到 3 个项目 - 这意味着第四次迭代无法查找项目。因此,您的迭代将在三个步骤后完成。

您可以在打印每个循环中的当前元素时看到这一点。要可视化效果,请使用enumerate 获取迭代的索引。请注意,它前进了一个索引,但值也移动了两个总偏移量:

>>> a = [1, 2, 3, 4, 5, 6]
... for idx, elem in enumerate(a):
...     print(elem, 'from', a)
...     print('      ', '   '*idx, '^')
...     del a[0]
...
1 from [1, 2, 3, 4, 5, 6]
        ^
3 from [2, 3, 4, 5, 6]
           ^
5 from [3, 4, 5, 6]
              ^

在迭代容器时修改容器通常没有明确定义。你应该迭代一个副本:

a = [1, 2, 3, 4, 5, 6]
for elem in a.copy():
    del a[0]
    print(a)

【讨论】:

很好的解释。谢谢@MisterMiyagi!一个问题——如果第一个代码是sn-p,最后一行应该是index += 1吗? @SupratimHaldar 是的,感谢您发现错误。修好了。【参考方案2】:

您将在循环的每次迭代中删除第一个元素 del a[0],因此迭代器分 3 步清空,因为它会移动到您在下一次迭代中删除的元素之后的元素。 您可以检查迭代器当前所在的元素,以及下面代码中的列表状态

a = [1, 2, 3, 4, 5, 6]
for elem in a:
    print(elem)
    del a[0]
    print(a)

输出是

1
[2, 3, 4, 5, 6]
3
[3, 4, 5, 6]
5
[4, 5, 6]

你可以把它想象成一个指向列表第一个元素的指针,每次迭代删除第一个元素时,指针会跳转2步,6个元素只能跳转3次。

通常,修改您正在迭代的同一个列表是一个坏主意。 但是如果你真的想要,你可以遍历列表的副本a[:]如果你真的想删除项目

a = [1, 2, 3, 4, 5, 6]
for elem in a[:]:
    del a[0]
    print(a)

输出是

[2, 3, 4, 5, 6]
[3, 4, 5, 6]
[4, 5, 6]
[5, 6]
[6]
[]

【讨论】:

嗯,好的,我想我明白了,谢谢。因此,在每次迭代之前都会检查列表 a。三次迭代后,列表中将只剩下三个元素,因此没有第四个元素要获取。起初我没有得到“迭代器在 3 个步骤中清空”的表达,但我知道我想我明白了。谢谢。 把它想象成一个指向列表第一个元素的指针,每次迭代删除第一个元素时,指针会跳转2步,6个元素只能跳转3次@Zois 谢谢!此外,del [0]a 上的小错字也不见了。它不允许我编辑它。【参考方案3】:

迭代同时从列表中删除元素是很棘手的。管理它的一种方法是反向遍历列表:

a = [1, 2, 3, 4, 5, 6]
for elem in reversed(a):
    print(a)
    del a[0]
print(a)

它会打印出来:

[1, 2, 3, 4, 5, 6]
[2, 3, 4, 5, 6]
[3, 4, 5, 6]
[4, 5, 6]
[5, 6]
[6]
[]

【讨论】:

有趣。您能否添加几行来解释这里发生了什么?我只是好奇,为什么reverse(a) 在这种情况下有效。 倒退不会破坏迭代器,@MisterMiyagi 在他的回答中更好地解释了这一点。 好的。谢谢@Óscar López!

以上是关于For 循环的迭代次数少于我在 Python 中的预期的主要内容,如果未能解决你的问题,请参考以下文章

根据 for 循环中的迭代次数向列表添加不同的值

python中的if循环怎么样?

Django模板:获取嵌套循环的总迭代次数

python跳过for循环

Python中的循环结构

Python—for循环和range()内建函数