关于python:yield如何捕获StopIteration异常?

How yield catches StopIteration exception?

示例函数终止的原因:

1
2
3
4
def func(iterable):
    while True:
        val = next(iterable)
        yield val

但如果我去掉yield语句函数,会引发stopIteration异常吗?

编辑:很抱歉误导你们。我知道发电机是什么以及如何使用它们。当然,当我说函数终止时,我并不是说要对函数进行急切的评估。我只是暗示,当我使用函数生成生成器时:

1
gen = func(iterable)

在func的情况下,它工作并返回相同的生成器,但在func2的情况下:

1
2
3
def func2(iterable):
    while True:
        val = next(iterable)

它将引发StopIteration,而不是无返回或无限循环。

让我更具体一点。itertools中有一个函数tee,相当于:

1
2
3
4
5
6
7
8
9
10
11
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语句的循环。当函数中没有任何项时,由于StopIteration异常,Gen函数将终止。但它正确地终止(不引发异常),即只停止循环。所以问题是:StopIteration在哪里处理?


要回答您的问题,关于StopIterationitertools.tee内部创建的gen生成器中被捕获的位置:不是的。由tee结果的使用者在迭代时捕获异常。好的。

首先,需要注意的是,生成器函数(在任何地方都是带有yield语句的任何函数)与正常函数有根本不同。而不是在调用函数时运行函数的代码,而是在调用函数时得到一个generator对象。只有当您在生成器上迭代时,才会运行代码。好的。

生成器函数不会在不提升StopIteration的情况下完成迭代(除非它会引发其他异常)。StopIteration是发电机发出的信号,它是完成的,不是可选的。如果在不引发任何内容的情况下到达return语句或生成器函数代码的末尾,python将为您引发StopIteration!好的。

这与常规函数不同,后者返回None,前提是它们到达末尾时没有返回任何其他内容。它与发电机的不同工作方式有关,正如我上面所描述的。好的。

下面是一个示例生成器函数,可以很容易地看到StopIteration是如何被提升的:好的。

1
2
3
4
def simple_generator():
    yield"foo"
    yield"bar"
    # StopIteration will be raised here automatically

以下是当你消费它时会发生的事情:好的。

1
2
3
4
5
6
7
8
9
10
>>> 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对象(不运行函数中的任何代码)。对generator对象的每个next调用都会运行代码,直到下一条yield语句,并返回生成的值。如果没有更多的数据,则增加StopIteration。好的。

现在,通常您不会看到StopIteration例外。这样做的原因是,您通常使用for循环内的生成器。for语句将自动反复调用next,直到StopIteration出现为止。它会捕捉并抑制你的StopIteration例外,所以你不需要为了处理它而与tryexcept块纠缠不清。好的。

for item in iterable: do_suff(item)这样的for循环几乎完全等同于这个while循环(唯一的区别是真正的for不需要临时变量来保存迭代器):好的。

1
2
3
4
5
6
7
8
9
iterator = iter(iterable)
try:
    while True:
        item = next(iterator)
        do_stuff(item)
except StopIteration:
    pass
finally:
    del iterator

您在顶部显示的gen生成器函数是一个例外。它使用迭代器产生的StopIteration异常,因为它使用自己的信号来完成对它的迭代。也就是说,它不是捕获StopIteration,然后跳出循环,而是让异常不被捕获(可能被更高级别的代码捕获)。好的。

与主要问题无关,还有一件事我想指出。在代码中,您在一个名为iterable的变量上调用next。如果您将这个名称作为文档来说明您将得到什么类型的对象,那么这不一定是安全的。好的。

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

编辑:我刚刚在谷歌搜索相关问题时找到了自己的答案,我想我应该更新一下,指出上面的答案在将来的Python版本中不会完全正确。PEP479使得允许StopIteration在未从生成器函数捕获的情况下冒泡成为一个错误。如果发生这种情况,python将把它变成一个RuntimeError异常。好的。

这意味着需要修改类似于itertools中使用StopIteration中断生成器函数的示例的代码。通常,您需要使用tryexcept捕获异常,然后执行return。好的。

因为这是一个向后不兼容的变化,它正逐渐被逐步采用。在python 3.5中,默认情况下,所有代码都将像以前一样工作,但是您可以使用from __future__ import generator_stop获得新的行为。在Python3.6中,代码仍然可以工作,但它会给出警告。在Python3.7中,新的行为将一直适用。好的。好啊。


当函数包含yield时,调用它实际上不执行任何操作,它只创建一个生成器对象。只有对该对象进行迭代才能执行代码。所以我猜你只是在调用函数,这意味着函数不会提升StopIteration,因为它永远不会被执行。

考虑到你的功能和一个不可替代的:

1
2
3
4
5
6
def func(iterable):
    while True:
        val = next(iterable)
        yield val

iterable = iter([1, 2, 3])

这是错误的称呼:

1
func(iterable)

这是正确的方法:

1
2
for item in func(iterable):
    # do something with item

您还可以将生成器存储在变量中,并对其调用next()(或以其他方式对其进行迭代):

1
2
3
4
5
gen = func(iterable)
print(next(gen))   # prints 1
print(next(gen))   # prints 2
print(next(gen))   # prints 3
print(next(gen))   # StopIteration

顺便说一下,编写函数的更好方法如下:

1
2
3
def func(iterable):
    for item in iterable:
        yield item

或者在python 3.3及更高版本中:

1
2
def func(iterable):
    yield from iter(iterable)

当然,真正的发电机很少如此微不足道。:-)


如果没有yield,您将迭代整个iterable,而不停止对val执行任何操作。while循环不捕获StopIteration异常。等效的for循环为:

1
2
3
def func(iterable):
    for val in iterable:
        pass

它确实捕获了StopIteration,并简单地退出循环,从而从函数返回。

您可以显式捕获异常:

1
2
3
4
5
6
def func(iterable):
    while True:
        try:
            val = next(iterable)
        except StopIteration:
            break


yield没有抓到StopIterationyield对函数的作用是使它成为一个生成器函数,而不是一个正则函数。因此,从函数调用返回的对象是一个可Iterable对象(当您使用next函数(由for循环隐式调用)请求它时,它计算下一个值)。如果您不使用yield语句,那么python将立即执行整个while循环,这最终耗尽了iterable(如果它是有限的),并在调用时立即引发StopIteration

考虑:

1
2
x = func(x for x in [])
next(x)  #raises StopIteration

一个for循环捕捉到异常——这就是它知道什么时候停止在你给它的iterable上调用next的方法。