为什么示例函数终止:
def func(iterable):
while True:
val = next(iterable)
yield val
但是如果我退出yield语句功能将引发StopIteration异常?
EDIT:对不起,误导你们。我知道什么是发电机以及如何使用它们。当然,当我说功能终止时,我并不是说急切地评估功能。我只是暗示当我使用函数来生成生成器时:
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语句。当it中没有任何项目时,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
生成器函数是一个例外。它使用它所消耗的迭代器产生的StopIteration
异常,因为它本身就表示已对其进行迭代。也就是说,与其捕获StopIteration
而不是跳出循环,不如不让异常被捕获(可能被更高级别的代码捕获了)。
与主要问题无关,我想指出另一件事。在您的代码中,您正在名为next
的变量上调用iterable
。如果您将该名称用作获取哪种类型的对象的文档,则不一定安全。
next
是iterator
协议的一部分,而不是iterable
(或container
)协议。它可能适用于某些可迭代对象(例如文件和生成器,因为这些类型是它们自己的迭代器),但不适用于其他可迭代对象(例如元组和列表)。更正确的方法是在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中,新行为将始终适用。
[当函数包含yield
时,调用该函数实际上不执行任何操作,它仅创建一个生成器对象。仅迭代此对象将执行代码。所以我的猜测是您只是在调用函数,这意味着该函数不会引发StopIteration
,因为它从未执行过。
没有yield
,您遍历整个iterable
而不停止对val
进行任何操作。 while
循环未捕获StopIteration
异常。等效的for
循环为:
yield
不能捕获StopIteration
。 yield
对您的功能的作用是使它成为生成器功能,而不是常规功能。因此,从函数调用返回的对象是一个可迭代的对象(当您使用next
函数要求下一个值时,该对象将计算下一个值(由for循环隐式调用))。如果不使用yield
语句,则python立即执行整个while
循环,最终将耗尽可迭代项(如果是有限的),并在调用它时将StopIteration
右移。
在Python 3.8上测试,块作为惰性生成器