yield如何捕获StopIteration异常?

Ser*_*nov 26 python yield stopiteration

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

def func(iterable):
    while True:
        val = next(iterable)
        yield val
Run Code Online (Sandbox Code Playgroud)

但如果我脱掉yield语句函数会引发StopIteration异常吗?

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

gen = func(iterable)
Run Code Online (Sandbox Code Playgroud)

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

def func2(iterable):
    while True:
        val = next(iterable)
Run Code Online (Sandbox Code Playgroud)

它引发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)
Run Code Online (Sandbox Code Playgroud)

事实上,有一些魔力,因为嵌套函数gen具有无限循环而没有break语句.函数结束因的StopIteration异常时,有中没有的项目.但它正确终止(没有引发异常),即只是停止循环.所以问题是:StopIteration在哪里处理?

Blc*_*ght 45

要回答你关于在内部创建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
Run Code Online (Sandbox Code Playgroud)

以下是您使用它时会发生的事情:

>>> 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
Run Code Online (Sandbox Code Playgroud)

调用simple_generator总是generator立即返回一个对象(不运行函数中的任何代码).next生成器对象上的每次调用都会运行代码直到下一个yield语句,并返回生成的值.如果没有更多的东西,就会StopIteration被提出来.

现在,通常你看不到StopIteration异常.原因是你通常在for循环中消耗生成器.一个for语句会自动调用next,直到唱完StopIteration得到提高.它将StopIteration为您捕获并抑制异常,因此您不需要使用try/ exceptblocks来处理它.

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
Run Code Online (Sandbox Code Playgroud)

gen您在顶部显示的生成器功能是一个例外.它使用StopIteration它正在消耗的迭代器产生的异常,因为它是自己的信号,它被迭代完成.也就是说,它不是捕获StopIteration然后突破循环,而是简单地让异常没有被捕获(可能被某些更高级别的代码捕获).

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

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

编辑:我刚刚在谷歌搜索中找到了我自己的答案,并找到了一个相关的问题,我想我会更新,指出上面的答案在未来的Python版本中并不完全正确.PEP 479允许StopIteration从生成器函数中冒出未被捕获的错误.如果发生这种情况,Python会把它变成RuntimeError异常.

这意味着需要修改类似于itertools使用a StopIteration来破坏生成器函数的示例的代码.通常你需要用try/ 来捕获异常except,然后再做return.

因为这是一个向后不兼容的变化,它正在逐步分阶段进行.在Python 3.5中,默认情况下所有代码都将像以前一样工作,但您可以使用它来获取新行为from __future__ import generator_stop.在Python 3.6中,代码仍然有效,但会发出警告.在Python 3.7中,新行为将始终适用.


kin*_*all 6

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

鉴于您的功能和可迭代:

def func(iterable):
    while True:
        val = next(iterable)
        yield val

iterable = iter([1, 2, 3])
Run Code Online (Sandbox Code Playgroud)

这是错误的方式来调用它:

func(iterable)
Run Code Online (Sandbox Code Playgroud)

这是正确的方法:

for item in func(iterable):
    # do something with item
Run Code Online (Sandbox Code Playgroud)

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

gen = func(iterable)
print(next(gen))   # prints 1
print(next(gen))   # prints 2
print(next(gen))   # prints 3
print(next(gen))   # StopIteration
Run Code Online (Sandbox Code Playgroud)

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

def func(iterable):
    for item in iterable:
        yield item
Run Code Online (Sandbox Code Playgroud)

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

def func(iterable):
    yield from iter(iterable)
Run Code Online (Sandbox Code Playgroud)

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


che*_*ner 5

如果没有yield,您将迭代整个过程iterable而无需停下来做任何事情val。循环while不会捕获StopIteration异常。等效的for循环是:

def func(iterable):
    for val in iterable:
        pass
Run Code Online (Sandbox Code Playgroud)

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

您可以显式捕获异常:

def func(iterable):
    while True:
        try:
            val = next(iterable)
        except StopIteration:
            break
Run Code Online (Sandbox Code Playgroud)