生成器表达式永远不会引发StopIteration [英] Generator expression never raises StopIteration

查看:133
本文介绍了生成器表达式永远不会引发StopIteration的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我自己的答案的启发,我甚至不了解它是如何运作的,请考虑以下事项:

Inspired by my own answer, I didn't even understand how it worked myself, consider the following:

def has22(nums):
    it = iter(nums)
    return any(x == 2 == next(it) for x in it)


>>> has22([2, 1, 2])
False

我预计 StopIteration 将被提出,因为一旦达到 2 next(it)将推进消耗的迭代器。但是,对于生成器表达式,似乎已完全禁用此行为!一旦发生这种情况,生成器表达式似乎立即 break

I expected a StopIteration to be raised, since upon reaching 2, next(it) would be advancing a consumed iterator. However it appears that this behavior has been completely disabled, for generator expressions only! The generator expression seems to immediately break once this happens.

>>> it = iter([2, 1, 2]); any(x == 2 == next(it) for x in it)
False
>>> it = iter([2, 1, 2]); any([x == 2 == next(it) for x in it])

Traceback (most recent call last):
  File "<pyshell#114>", line 1, in <module>
    it = iter([2, 1, 2]); any([x == 2 == next(it) for x in it])
StopIteration
>>> def F(nums):
        it = iter(nums)
        for x in it:
            if x == 2 == next(it): return True


>>> F([2, 1, 2])

Traceback (most recent call last):
  File "<pyshell#117>", line 1, in <module>
    F([2, 1, 2])
  File "<pyshell#116>", line 4, in F
    if x == 2 == next(it): return True
StopIteration

即便如此!

>>> it=iter([2, 1, 2]); list((next(it), next(it), next(it), next(it))for x in it)
[]

所以我想我的问题是,为什么对生成器表达式启用了此行为?

So I guess my question is, why is this behavior enabled for generator expressions?

注意: 3.x

推荐答案

开发人员已经决定允许这是一个错误,因为它可以掩盖晦涩的错误。因此,接受 PEP 479 意味着这种情况即将消失。

The devs have decided that allowing this was a mistake because it can mask obscure bugs. Because of that, the acceptance of PEP 479 means this is going away.

在Python 3.5中,如果你从__future__ import generator_stop 执行,默认情况下在Python 3.7中,问题中的示例将以 RuntimeError 失败。使用一些itertools魔法你仍然可以达到相同的效果(允许 nums 不能预先计算):

In Python 3.5 if you do from __future__ import generator_stop, and in Python 3.7 by default, the example in the question will fail with a RuntimeError. You could still achieve the same effect (allowing nums to not be precomputed) with some itertools magic:

from itertools import tee, islice

def has22(nums):
    its = tee(nums, 2)
    return any(x == y == 2 for x, y in 
               zip(its[0], islice(its[1], 1, None)))

它首先起作用的原因与发电机的工作方式有关。你可以想到这个for循环:

The reason it ever worked in the first place has to do with how generators work. You can think of this for loop:

for a in b:
    # do stuff

与(大致)相当于:

b = iter(b) 
while True:
    try:
        a = next(b)
    except StopIteration:
        break
    else:
        # do stuff

现在,所有的例子都有 2 for循环嵌套在一起(一个在生成器表达式中,一个在函数中使用它),因此当外循环执行其 next 调用时,内循环迭代一次。当内循环中的'#do stuff'为时,会发生什么?提高StopIteration

Now, all the examples have two for loops nested together (one in the generator expression, one in the function consuming it), so that the inner loop iterates once when the outer loop performs its next call. What happens when the '# do stuff' in the inner loop is raise StopIteration?

>>> def foo(): raise StopIteration
>>> list(foo() for x in range(10))
[]

异常传播出内部循环,因为它不在其内部,并被外部循环捕获。在新行为下,Python将拦截即将从生成器传播的 StopIteration 并将其替换为 RuntimeError 将不会被包含for循环捕获

The exception propagates out of the inner loop, since it isn't in its guard, and gets caught by the outer loop. Under the new behavior, Python will intercept a StopIteration that is about to propagate out of a generator and replace it with a RuntimeError, which won't be caught by the containing for loop.

这也暗示了这样的代码:

This also has the implication that code like this:

def a_generator():
     yield 5
     raise StopIteration

也会失败,邮件列表线程给人的印象是,无论如何这被认为是糟糕的形式。正确的方法是:

will also fail, and the mailing list thread gives the impression that this was considered bad form anyway. The proper way to do this is:

def a_generator():
    yield 5
    return

正如您所指出的,列表推导已经表现得不同:

As you pointed out, list comprehensions already behave differently:

>>> [foo() for x in range(10)]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <listcomp>
  File "<stdin>", line 1, in foo
StopIteration

这有点是一个实现细节泄漏 - 列表推导转换为对 list 的调用,具有等效的生成器表达式,并且显然正在做所以会导致大的性能损失考虑禁止。

This is somewhat an implementation detail leaking - list comprehensions don't get transformed into a call to list with an equivalent generator expression, and apparently doing so would cause large performance penalties that the powers that be consider prohibitive.

这篇关于生成器表达式永远不会引发StopIteration的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆