首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >产量如何捕获StopIteration异常?

产量如何捕获StopIteration异常?
EN

Stack Overflow用户
提问于 2013-05-09 15:20:31
回答 5查看 76.9K关注 0票数 44

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

代码语言:javascript
复制
def func(iterable):
    while True:
        val = next(iterable)
        yield val

但是如果我去掉收益陈述函数会引起StopIteration异常吗?

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

代码语言:javascript
复制
gen = func(iterable)

对于func,它工作并返回相同的生成器,但对于func2:

代码语言:javascript
复制
def func2(iterable):
    while True:
        val = next(iterable)

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

让我说得更具体些。在http://docs.python.org/2/library/itertools.html#itertools.tee迭代工具中有一个函数,相当于:

代码语言:javascript
复制
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?

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2013-05-09 23:47:37

注意:这个问题(以及我回答的最初部分)对于3.7之前的版本来说是非常有意义的。由于 佩普479中描述的更改,被问到的行为不再发生在3.7或更高版本中。因此,这个问题和最初的答案只是作为历史文物而真正有用的。在PEP被接受之后,我在答案的底部添加了一个更适合于Python.Python的现代版本的部分。

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

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

生成器函数不会在不引发StopIteration的情况下完成迭代(除非它会引发其他异常)。StopIteration是从生成器发出的信号,它不是可选的。如果您到达return语句或生成器函数代码的末尾而不引发任何东西,Python将为您引发StopIteration

这与常规函数不同,常规函数如果到达终点而不返回任何其他内容,则返回None。正如我前面所描述的,它与生成器工作的不同方式相联系。

下面是一个示例生成器函数,它可以很容易地查看StopIteration是如何引发的:

代码语言:javascript
复制
def simple_generator():
    yield "foo"
    yield "bar"
    # StopIteration will be raised here automatically

下面是当您使用它时会发生的情况:

代码语言:javascript
复制
>>> 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不需要临时变量来容纳迭代器):

代码语言:javascript
复制
iterator = iter(iterable)
try:
    while True:
        item = next(iterator)
        do_stuff(item)
except StopIteration:
    pass
finally:
    del iterator

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

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

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

我刚刚在谷歌搜索一个相关问题时找到了我自己的答案,我觉得我应该更新一下,指出上面的答案在现代的版本中是不正确的。

佩普479使允许StopIteration从生成器函数中隐藏起来是错误的。如果发生这种情况,Python将将其转换为RuntimeError异常。这意味着需要修改代码,比如在使用itertools的旧版本中使用StopIteration来脱离生成器函数的示例。通常,您需要用try/exceptreturn捕获异常。

因为这是一种向后不相容的变化,所以它是逐步实施的。在Python3.5中,所有代码在默认情况下都可以正常工作,但是您可以使用from __future__ import generator_stop获得新的行为。在Python3.6中,未经修改的代码仍然可以工作,但它会发出警告。在Python3.7及更高版本中,新行为始终适用。

票数 67
EN

Stack Overflow用户

发布于 2013-05-09 15:24:24

当一个函数包含yield时,调用它实际上不会执行任何操作,它只是创建一个生成器对象。只有对这个对象进行迭代才能执行代码。因此,我的猜测是,您只是在调用该函数,这意味着该函数不会引发StopIteration,因为它从未被执行。

给定您的函数,并具有可迭代性:

代码语言:javascript
复制
def func(iterable):
    while True:
        val = next(iterable)
        yield val

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

这是错误的称呼方式:

代码语言:javascript
复制
func(iterable)

这是正确的方法:

代码语言:javascript
复制
for item in func(iterable):
    # do something with item

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

代码语言:javascript
复制
gen = func(iterable)
print(next(gen))   # prints 1
print(next(gen))   # prints 2
print(next(gen))   # prints 3
print(next(gen))   # StopIteration

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

代码语言:javascript
复制
def func(iterable):
    for item in iterable:
        yield item

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

代码语言:javascript
复制
def func(iterable):
    yield from iter(iterable)

当然,真正的发电机很少如此琐碎。:-)

票数 9
EN

Stack Overflow用户

发布于 2013-05-09 15:23:23

如果没有yield,则可以迭代整个iterable,而无需停止使用val进行任何操作。while循环不捕获StopIteration异常。一个等价的for循环是:

代码语言:javascript
复制
def func(iterable):
    for val in iterable:
        pass

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

您可以显式地捕获异常:

代码语言:javascript
复制
def func(iterable):
    while True:
        try:
            val = next(iterable)
        except StopIteration:
            break
票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/16465313

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档