为什么我的except块无法捕获StopIteration异常?

时间:2020-05-22 02:50:33

标签: python

我要对该程序执行的操作是希望它通过使用递归一次又一次地返回“是”和“否”。请帮忙!我在做错什么吗?

def yes_or_no():
for x in ("yes","no"):
    try:
        yield x
    except (StopIteration):
        return yes_or_no()

gen =  yes_or_no()
print(next(gen))
print(next(gen))
print(next(gen))

到达第三张打印纸后,即使我以为我在错误处理中发现了它,它也会显示StopIteration?

3 个答案:

答案 0 :(得分:1)

StopIteration调用中不会引发yield异常,因此您不会通过try / except设置来捕获它。

通过在函数中使用yield语句,您已将其转换为生成器(您似乎可以理解)。每次评估生成器时,它将以上次完成的成品率重新输入,并继续进行直到下一个成品率或函数完成为止。因此,在您的第三个next()上,执行将在yield处继续,回到for循环,看看它完成了,然后继续进行,这只是结束了。在这种情况下的功能。该函数将返回,因此生成器将提高其StopIteration。

我不建议您对这个任务使用递归;只需在(“ yes,” no“)循环周围使用外部无限循环(或者更好的是,来自itertools的东西)。

如果您真的想使用递归,那么您可能想尝试

def yes_or_no():
    for x in ("yes", "no"):
        yield x
    yield from yes_or_no()

我认为yield from位需要Python> = 3.3。

答案 1 :(得分:1)

循环内的

yield x永远不会raise StopIteration,因此您无法捕获它。 Python for循环是使用StopIteration实现的,确实如此,但是在那里引发的实例在被您处理之前就被捕获了

使您的想法起作用的方法是...让循环运行,然后产生其余元素。递归:

def yes_or_no():
    for x in ("yes","no"):
        yield x
    yield from yes_or_no()

顺便说一句:yield from是您需要的,以便继续产生递归调用产生的元素。生成器的return值不能帮助您产生更多元素。具有讽刺意味的是,实际上,它设置了导致生成器元素不足的结果的异常:

>>> def example():
...     yield 1
...     return 2
...
>>> x = example()
>>> next(x)
1
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration: 2

(当然,您的整个目标是让yes_or_no() 不是用尽所有元素,所以。)

无论如何,为此使用递归是一个坏主意,因为您人为地限制了递归的工作时间(直到堆栈帧用完)。只是迭代:

def yes_or_no():
    while True:
        yield 'yes'
        yield 'no'

或使用itertools

>>> import itertools
>>> x = itertools.cycle(('yes', 'no'))
>>> next(x)
'yes'
>>> next(x)
'no'
>>> next(x)
'yes'
>>> next(x)
'no'

那么如何您如何在用户代码中充分利用StopIteration?通过在 iterator 实现中显式提高它(尽管最明显的方法已经为您完成),或者通过在手动推进迭代器时捕获它使用next。例子:

class SingletonIterator:
    """Proxy iterator to allow 'iteration' over a fictitious sequence
    holding a single element, the `value`."""
    def __init__(self, value):
        self._value = value
        self._yielded = False

    def __iter__(self):
        return self

    def __next__(self):
        if self._yielded:
            raise StopIteration
        self._yielded = True
        return self._value


def first(predicate, sequence):
    """The first element `e` of `sequence` satisfying `predicate(e)`.
    Raises ValueError for an empty sequence."""
    try:
        return next(e for e in sequence if predicate(e))
    except StopIteration:
        raise ValueError('empty sequence')

答案 2 :(得分:0)

这是一个简单的递归解决方案:

def yes_or_no():
    yield from ('yes', 'no')
    yield from yes_or_no()

编辑:

itertools模块具有函数cycle,该函数接受迭代器并返回生成器,该生成器循环其输入。

import itertools
def yes_or_no():
    yield from itertools.cycle(("yes", "no"))