yield如何捕获StopIteration异常?

2024-04-20 11:34:46 发布

您现在位置:Python中文网/ 问答频道 /正文

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

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


Tags: 函数intruefordefitvaliterable
3条回答

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

考虑到你的功能和一个iterable:

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

或者在Python3.3及更高版本中:

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

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

要回答关于StopIterationitertools.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异常,因此您不需要处理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然后中断循环,而是简单地让异常不被发现(可能被一些更高级别的代码捕获)。

与主要问题无关,还有一件事我想指出。在代码中,您正在对名为iterable的变量调用next。如果将该名称作为将获得的对象类型的文档,则这不一定安全。

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

编辑:我在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循环是:

def func(iterable):
    for val in iterable:
        pass

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

可以显式捕获异常:

def func(iterable):
    while True:
        try:
            val = next(iterable)
        except StopIteration:
            break

相关问题 更多 >