为什么在示例函数中终止:

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

最佳答案

要回答有关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生成器功能是一个异常(exception)。它使用它所消耗的迭代器产生的StopIteration异常,因为它本身就是要对其进行迭代的信号。也就是说,与其捕获StopIteration而不是跳出循环,不如不让异常被捕获(大概被更高级别的代码捕获)了。

与主要问题无关,我想指出另一件事。在您的代码中,您正在对名为next的变量调用iterable。如果您将该名称作为获取哪种类型的对象的文档,则不一定安全。
nextiterator协议(protocol)的一部分,而不是iterable(或container)协议(protocol)。它可能适用于某些可迭代对象(例如文件和生成器,因为这些类型是它们自己的迭代器),但不适用于其他可迭代对象(例如元组和列表)。更正确的方法是在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中,新行为将始终适用。

10-08 08:09