首页 > 解决方案 > 在 for 循环期间修改可迭代的大小 - 如何确定循环?

问题描述

在 python 文档(我发现)中的两个 地方提到了 for 循环。我确实尝试在cpythonfor中找到循环的源代码,但无济于事。

这就是我想要理解的内容:我曾假设 for 循环是一种while i <= len(iterable) then loopor if i <= len(iterable) then loop:。我不确定情况是否如此,原因如下:

y = [1, 2, 3, 4]
for x in y:
  print(y)
  print(y.pop(0))

Output:
[1, 2, 3, 4]
1
[2, 3, 4]
2

知道你不应该在循环遍历它时修改它。我知道。但是,这仍然不是随机结果 - 每次运行此代码时都会发生:2 个循环。如果您pop()改为运行,您还将获得 2 个循环。

也许更奇怪的是,您似乎可靠地获得len(y)+1//2了循环(至少使用.pop(),我没有尝试过太多其他测试):

根据 Python 文档:

笔记

当序列被循环修改时有一个微妙之处(这只会发生在可变序列中,例如列表)。内部计数器用于跟踪接下来使用哪个项目,并在每次迭代时递增。当此计数器达到序列的长度时,循环终止。这意味着如果套件从序列中删除当前(或前一个)项目,则将跳过下一个项目(因为它获取已处理的当前项目的索引)。同样,如果套件在当前项目之前插入序列中的项目,则当前项目将在下一次循环中再次被处理。这可能会导致讨厌的错误,可以通过使用整个序列的切片制作临时副本来避免这些错误,例如,

for x in a[:]:
    if x < 0: a.remove(x)

任何人都可以解释 Python 在循环通过在循环期间修改的可迭代对象时使用的逻辑吗?iterStopIteration__getitem__(i)和是如何IndexError计算的?不是列表的迭代器呢?最重要的是,这是/在文档中的哪里?

正如@Yang K 建议的那样:

y = [1, 2, 3, 4, 5, 6, 7]
for x in y:
  print("y: {}, y.pop(0): {}".format(y, y.pop(0)))
  print("x: {}".format(x))

# Output
y: [2, 3, 4, 5, 6, 7], y.pop(0): 1
x: 1
y: [3, 4, 5, 6, 7], y.pop(0): 2
x: 3
y: [4, 5, 6, 7], y.pop(0): 3
x: 5
y: [5, 6, 7], y.pop(0): 4
x: 7

标签: pythonmutation

解决方案


循环执行直到 iterable 说它没有更多元素。两个循环之后,iterable 已经遍历了两个元素,并且丢失了两个元素,这意味着它到了它的末尾,循环终止。

您的代码等效于:

y = [1, 2, 3, 4]
i = iter(y)
while True:
    try:
        x=next(i)
    except StopIteration:
        break
    print(y)
    print(y.pop(0))

列表迭代器保存下一个要读取的索引。在第三个循环中,列表是[3, 4]next(i)并且需要读取y[2],这是不可能的,所以nextraises StopIteration,结束循环。

编辑关于您的其他问题:

iterStopIteration__getitem__(i)和是如何IndexError计算的?

前两个如上所述:它定义了for循环。或者,如果你愿意,它是 的合约iter:它会产生东西,直到它停止StopIteration

后两者,我认为根本不参与,因为列表迭代器是用 C 实现的;例如,检查迭代器是否耗尽,直接将当前索引与 进行比较PyList_GET_SIZE,直接查看->ob_size字段;它不再通过 Python。显然,您可以创建一个完全使用纯 Python 的列表迭代器,并且您可能会使用它len来执行检查,或者捕获IndexError并再次让底层 C 代码对->ob_size.

不是列表的迭代器呢?

您可以将任何对象定义为可迭代的。当您调用时iter(obj),它与调用相同obj.__iter__()。预计这将返回一个迭代器,该迭代器知道如何处理i.__next__()(这就是next(i)翻译的内容)。我相信 dicts 通过在其键列表中设置索引来迭代(我认为,尚未检查)。如果您编写代码,您可以创建一个迭代器,它可以做任何您想做的事情。例如:

class AlwaysEmpty:
    def __iter__(self):
        return self
    def __next__(self):
        raise StopIteration

for x in AlwaysEmpty():
    print("there was something")

可以预见,不会打印任何内容。

最重要的是,这是/在文档中的哪里?

迭代器类型


推荐阅读