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


要回答你关于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异常,所以你不需要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的变量。 如果您将该名称作为您将获得的对象类型的文档,则这不一定安全。

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

编辑:我刚刚在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

上一篇: How yield catches StopIteration exception?

下一篇: Difference between Python's Generators and Iterators