注意:这个问题(以及我对它的回答的原始部分)只对 3.7 之前的 Python 版本真正有意义。由于PEP 479中描述的更改,所询问的行为在 3.7 及更高版本中不再发生。因此,这个问题和原始答案仅作为历史文物才真正有用。在 PEP 被接受后,我在答案的底部添加了一个与现代版本的 Python 更相关的附加部分。
要回答您关于在内部创建的生成器StopIteration
中捕获的位置的问题:它没有。由结果的消费者在迭代时捕获异常。gen
itertools.tee
tee
首先,重要的是要注意生成器函数(它是在任何地方都有yield
语句的任何函数)与普通函数根本不同。而不是在调用函数时运行该函数的代码,而是generator
在调用该函数时获得一个对象。只有当您迭代生成器时,您才会运行代码。
生成器函数在不引发的情况下永远不会完成迭代StopIteration
(除非它引发其他异常)。StopIteration
是来自生成器的信号,表明它已经完成,它不是可选的。如果你return
在没有引发任何东西的情况下到达一个语句或生成器函数代码的结尾,Python 会StopIteration
为你引发!
这与常规函数不同,常规函数None
在到达末尾时返回而不返回任何其他内容。如上所述,它与生成器的不同工作方式有关。
这是一个示例生成器函数,可以很容易地看到如何StopIteration
引发:
def simple_generator():
yield "foo"
yield "bar"
# StopIteration will be raised here automatically
这是您食用它时会发生的情况:
>>> g = simple_generator()
>>> next(g)
'foo'
>>> next(g)
'bar'
>>> next(g)
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
next(g)
StopIteration
调用simple_generator
总是generator
立即返回一个对象(不运行函数中的任何代码)。对生成器对象的每次调用next
都会运行代码直到下一条yield
语句,并返回产生的值。如果没有更多可以得到,StopIteration
则被提出。
现在,通常您不会看到StopIteration
异常。这样做的原因是您通常在for
循环中使用生成器。语句将for
自动next
一遍又一遍地调用,直到StopIteration
被提出。它会为你捕获并抑制StopIteration
异常,所以你不需要弄乱try
/except
块来处理它。
for
像这样的循环for item in iterable: do_suff(item)
几乎完全等同于这个while
循环(唯一的区别是 realfor
不需要临时变量来保存迭代器):
iterator = iter(iterable)
try:
while True:
item = next(iterator)
do_stuff(item)
except StopIteration:
pass
finally:
del iterator
gen
您在顶部显示的生成器函数是一个例外。它使用StopIteration
它所消耗的迭代器产生的异常作为它自己的信号,表明它已经完成了迭代。也就是说,它不是捕获StopIteration
然后跳出循环,而是简单地让异常未被捕获(可能被一些更高级别的代码捕获)。
与主要问题无关,我想指出另一件事。在您的代码中,您正在调用next
一个名为iterable
. 如果您将该名称作为您将获得的对象类型的文档,这不一定是安全的。
next
是iterator
协议的一部分,而不是iterable
(或container
)协议。它可能适用于某些类型的可迭代对象(例如文件和生成器,因为这些类型是它们自己的迭代器),但对于其他可迭代对象(例如元组和列表)将失败。更正确的方法是调用iter
你的iterable
值,然后调用next
你收到的迭代器。(或者只是使用for
循环,它会在适当的时候为你调用iter
和调用!)next
我刚刚在 Google 搜索相关问题时找到了自己的答案,我觉得我应该更新以指出上述答案在现代 Python 版本中并不正确。
PEP 479将允许 aStopIteration
从生成器函数中冒泡而不被捕获是错误的。如果发生这种情况,Python 会将其变为RuntimeError
异常。这意味着需要修改像旧版本中itertools
使用 aStopIteration
来突破生成器函数的示例的代码。通常你需要用try
/except
然后捕获异常return
。
因为这是一个向后不兼容的变化,所以它逐渐被分阶段实施。在 Python 3.5 中,默认情况下所有代码都像以前一样工作,但您可以使用from __future__ import generator_stop
. 在 Python 3.6 中,未修改的代码仍然可以工作,但会发出警告。在 Python 3.7 及更高版本中,新行为始终适用。