为什么在示例函数中终止:
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)
它将引发stopietition而不是Nonereturn或无限循环。
让我更具体一点。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在哪里处理?
当函数包含
yield
时,调用它实际上不会执行任何操作,它只会创建一个生成器对象。只有遍历此对象才能执行代码。所以我猜你只是在调用这个函数,这意味着这个函数不会引发StopIteration
,因为它永远不会被执行。考虑到你的功能和一个iterable:
这是错误的称呼:
这是正确的方式:
您还可以将生成器存储在变量中并对其调用
next()
(或以其他方式迭代):顺便说一下,编写函数的更好方法如下:
或者在Python3.3及更高版本中:
当然,真正的发电机很少如此微不足道。:-)
要回答关于
StopIteration
在itertools.tee
内部创建的gen
生成器中被捕获的位置的问题:它不是,而是由tee
结果的使用者在迭代时捕获异常。首先,需要注意的是,生成器函数(在任何地方都是带有
yield
语句的任何函数)与普通函数有本质的不同。而不是在调用函数时运行它的代码,而是在调用函数时获得一个generator
对象。只有在遍历生成器时才会运行代码。生成器函数在不引发
StopIteration
的情况下永远不会完成迭代(除非它引发其他异常)。StopIteration
是从生成器发出的信号,它已经完成,并且不是可选的。如果您到达一个return
语句或生成器函数的代码末尾而不引发任何东西,Python将为您引发StopIteration
!这与常规函数不同,常规函数在到达末尾时返回
None
,而不返回任何其他内容。它与发电机的不同工作方式有关,如我上面所述。下面是一个示例生成器函数,可以很容易地看到
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
不需要临时变量来保存迭代器):您在顶部显示的
gen
生成器函数是一个例外。它使用迭代器生成的StopIteration
异常,它正在使用迭代器作为自己完成迭代的信号。也就是说,它不是捕获StopIteration
然后中断循环,而是简单地让异常不被发现(可能被一些更高级别的代码捕获)。与主要问题无关,还有一件事我想指出。在代码中,您正在对名为
iterable
的变量调用next
。如果将该名称作为将获得的对象类型的文档,则这不一定安全。next
是iterator
协议的一部分,而不是iterable
(或container
)协议。它可能适用于某些类型的iterable(如文件和生成器,因为这些类型是它们自己的迭代器),但对于其他iterable(如元组和列表)则会失败。更正确的方法是对iterable
值调用iter
,然后对接收到的迭代器调用next
。(或者只使用for
循环,它在适当的时候同时为您调用iter
和next
!)编辑:我在Google搜索相关问题时找到了自己的答案,我想我应该更新一下,指出在未来的Python版本中,上面的答案不会完全正确。PEP 479使允许
StopIteration
从生成器函数中意外冒泡成为错误。如果发生这种情况,Python将把它变成一个RuntimeError
异常。这意味着需要修改像
itertools
中使用StopIteration
来中断生成器函数的示例这样的代码。通常需要用try
/except
捕获异常,然后执行return
。因为t他是一个前后矛盾的变化,正在逐步进行中。在Python3.5中,默认情况下所有代码都将像以前一样工作,但是您可以使用
from __future__ import generator_stop
获得新的行为。在Python3.6中,代码仍然可以工作,但会发出警告。在Python3.7中,新的行为将一直应用。如果没有
yield
,您将遍历整个iterable
,而不会停止对val
执行任何操作。while
循环不捕获StopIteration
异常。等效的for
循环是:它捕获
StopIteration
,并简单地退出循环,从而从函数返回。可以显式捕获异常:
相关问题 更多 >
编程相关推荐