为什么在示例函数中终止:
def func(iterable):
while True:
val = next(iterable)
yield val
但是如果我离开yield语句功能会引发StopIteration异常?
编辑:抱歉,误导你们。我知道什么是发电机以及如何使用它们。当然,当我说功能终止时,我并不是说急切地评估功能。我只是暗示当我使用函数来生成生成器时:
gen = func(iterable)
在func的情况下,它可以工作并返回相同的生成器,但在func2的情况下:
def func2(iterable):
while True:
val = next(iterable)
它引发StopIteration而不是None返回或无限循环。
让我更具体一点。 itertools 中有一个函数tee,它等效于:
def tee(iterable, n=2):
it = iter(iterable)
deques = [collections.deque() for i in range(n)]
def gen(mydeque):
while True:
if not mydeque: # when the local deque is empty
newval = next(it) # fetch a new value and
for d in deques: # load it to all the deques
d.append(newval)
yield mydeque.popleft()
return tuple(gen(d) for d in deques)
实际上,这有些神奇,因为嵌套函数gen具有无限循环而没有break语句。当其中没有任何项目时,gen函数由于StopIteration异常而终止。但是它可以正确终止(没有引发异常),即只是停止循环。 所以问题是:StopIteration在哪里处理?
最佳答案
要回答有关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
循环(唯一的区别是,真正的for
不需要临时变量来保存迭代器):iterator = iter(iterable)
try:
while True:
item = next(iterator)
do_stuff(item)
except StopIteration:
pass
finally:
del iterator
您在顶部显示的
gen
生成器功能是一个异常(exception)。它使用它所消耗的迭代器产生的StopIteration
异常,因为它本身就是要对其进行迭代的信号。也就是说,与其捕获StopIteration
而不是跳出循环,不如不让异常被捕获(大概被更高级别的代码捕获)了。与主要问题无关,我想指出另一件事。在您的代码中,您正在对名为
next
的变量调用iterable
。如果您将该名称作为获取哪种类型的对象的文档,则不一定安全。next
是iterator
协议(protocol)的一部分,而不是iterable
(或container
)协议(protocol)。它可能适用于某些可迭代对象(例如文件和生成器,因为这些类型是它们自己的迭代器),但不适用于其他可迭代对象(例如元组和列表)。更正确的方法是在iter
值上调用iterable
,然后在收到的迭代器上调用next
。 (或者只使用for
循环,该循环会在适当的时候为您调用iter
和next
!)编辑:我刚刚在Google搜索中找到了一个相关问题的答案,我想我要指出一点,以上答案在将来的Python版本中将不完全正确。 PEP 479允许
StopIteration
冒泡生成器函数未捕获的错误。如果发生这种情况,Python会将其改为RuntimeError
异常。这意味着需要修改类似于
itertools
中使用StopIteration
的示例的代码。通常,您需要使用try
/except
捕获异常,然后执行return
。由于这是一个向后不兼容的更改,因此正在逐步实现。在Python 3.5中,默认情况下所有代码都将像以前一样工作,但是您可以使用
from __future__ import generator_stop
获得新的行为。在Python 3.6中,代码仍然可以使用,但是会给出警告。在Python 3.7中,新行为将始终适用。