收益如何捕获StopIteration异常?

问题描述 投票:29回答:5

为什么示例函数终止:

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在哪里处理?

python yield stopiteration
5个回答
47
投票

要回答有关StopIterationgen内部创建的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。如果您将该名称用作获取哪种类型的对象的文档,则不一定安全。

nextiterator协议的一部分,而不是iterable(或container)协议。它可能适用于某些可迭代对象(例如文件和生成器,因为这些类型是它们自己的迭代器),但不适用于其他可迭代对象(例如元组和列表)。更正确的方法是在iter值上调用iterable,然后在收到的迭代器上调用next。 (或者只使用for循环,在适当的时候可以同时调用iternext!)

编辑:我刚刚在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中,新行为将始终适用。


9
投票

[当函数包含yield时,调用该函数实际上不执行任何操作,它仅创建一个生成器对象。仅迭代此对象将执行代码。所以我的猜测是您只是在调用函数,这意味着该函数不会引发StopIteration,因为它从未执行过。


4
投票

没有yield,您遍历整个iterable而不停止对val进行任何操作。 while循环未捕获StopIteration异常。等效的for循环为:


1
投票

yield不能捕获StopIterationyield对您的功能的作用是使它成为生成器功能,而不是常规功能。因此,从函数调用返回的对象是一个可迭代的对象(当您使用next函数要求下一个值时,该对象将计算下一个值(由for循环隐式调用))。如果不使用yield语句,则python立即执行整个while循环,最终将耗尽可迭代项(如果是有限的),并在调用它时将StopIteration右移。


0
投票

在Python 3.8上测试,块作为惰性生成器

© www.soinside.com 2019 - 2024. All rights reserved.