yield如何捕获StopIteration异常?
为什么在示例函数中终止:
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而不是无返回或无限循环。
让我更具体一些。 itertools中有一个函数,它相当于:
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有没有中断语句的无限循环。 gen函数在没有项目的情况下由于StopIteration异常而终止。 但它会正确终止(不会引发异常),即只是停止循环。 所以问题是 :StopIteration在哪里被处理?
要回答你关于StopIteration
在itertools.tee
内创建的gen
生成器中被捕获到的问题:它不会。 当迭代结果发生异常时,由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
异常,所以你不需要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
generator函数是一个例外。 它使用它正在消耗的迭代器产生的StopIteration
异常,因为它是自己的信号,它被完成迭代。 也就是说,不是捕获StopIteration
然后跳出循环,而是让异常不被捕获(大概是被更高级别的代码捕获)。
与主要问题无关,还有一件事我想指出。 在你的代码中, next
调用next
名为iterable
的变量。 如果您将该名称作为您将获得的对象类型的文档,则这不一定安全。
next
是iterator
协议的一部分,而不是iterable
(或container
)协议。 它可能适用于某些类型的迭代器(例如文件和生成器,因为这些类型是它们自己的迭代器),但对于其他迭代器(例如元组和列表)会失败。 更正确的方法是在你的iterable
值上调用iter
,然后在你接收到的迭代器上调用next
。 (或者只是使用for
循环,在适当的时候为你调用iter
和next
!)
编辑:我刚刚在Google搜索中找到了我自己的答案,并且我想我会更新以指出上述答案在未来的Python版本中不会完全正确。 PEP 479使得StopIteration
不能从生成器函数中捕获, StopIteration
导致错误。 如果发生这种情况,Python会将它变成一个RuntimeError
异常。
这意味着类似itertools
中使用StopIteration
突破生成器函数的示例的代码将需要修改。 通常你需要捕获异常与try
/ except
,然后就return
。
因为这是一个倒退不相容的变化,所以逐步逐步实施。 在Python 3.5中,默认情况下所有代码都将像以前一样工作,但您可以from __future__ import generator_stop
获取新行为。 在Python 3.6中,代码仍然可以工作,但会发出警告。 在Python 3.7中,新的行为将始终适用。
当一个函数包含yield
,调用它并不会真正执行任何操作,它只会创建一个生成器对象。 只有遍历这个对象才会执行代码。 所以我的猜测是你只是调用函数,这意味着函数不会引发StopIteration
因为它永远不会被执行。
鉴于你的功能,和一个可迭代的:
def func(iterable):
while True:
val = next(iterable)
yield val
iterable = iter([1, 2, 3])
这是调用它的错误方式:
func(iterable)
这是正确的方法:
for item in func(iterable):
# do something with item
您也可以将生成器存储在变量中并调用next()
(或以其他方式遍历它):
gen = func(iterable)
print(next(gen)) # prints 1
print(next(gen)) # prints 2
print(next(gen)) # prints 3
print(next(gen)) # StopIteration
顺便说一句,编写函数的更好方法如下:
def func(iterable):
for item in iterable:
yield item
或者在Python 3.3及更高版本中:
def func(iterable):
yield from iter(iterable)
当然,真正的发电机很少如此微不足道。 :-)
如果没有yield
,您可以遍历整个iterable
而不必停止使用val
进行任何操作。 while
循环不捕获StopIteration
异常。 一个等效for
循环将是:
def func(iterable):
for val in iterable:
pass
它捕获StopIteration
并简单地退出循环并从函数返回。
您可以明确地捕获异常:
def func(iterable):
while True:
try:
val = next(iterable)
except StopIteration:
break
链接地址: http://www.djcxy.com/p/53475.html