生成器表达式永远不会引发StopIteration

jam*_*lak 12 python iterator generator

我自己答案的启发,甚至不了解它是如何起作用的,请考虑以下几点:

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


>>> has22([2, 1, 2])
False
Run Code Online (Sandbox Code Playgroud)

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

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

即便如此!

>>> it=iter([2, 1, 2]); list((next(it), next(it), next(it), next(it))for x in it)
[]
Run Code Online (Sandbox Code Playgroud)

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

注意:相同的行为3.x

lvc*_*lvc 5

开发人员认为允许这样做是一个错误,因为它可以掩盖晦涩的错误。因此,PEP 479的接受 意味着这种情况正在消失。

如果您这样做的话from __future__ import generator_stop,在Python 3.5中,默认情况下在Python 3.7中,问题中的示例将失败并带有RuntimeError。您仍然nums可以使用itertools魔术来达到相同的效果(允许不进行预先计算):

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

它最初起作用的原因与发电机的工作方式有关。您可以想到以下for循环:

for a in b:
    # do stuff
Run Code Online (Sandbox Code Playgroud)

由于(大致)等同于此:

b = iter(b) 
while True:
    try:
        a = next(b)
    except StopIteration:
        break
    else:
        # do stuff
Run Code Online (Sandbox Code Playgroud)

现在,所有示例都将两个 for循环嵌套在一起(一个在生成器表达式中,一个在使用它的函数中),这样,当外部循环执行其next调用时,内部循环将迭代一次。当内循环中的“做东西”是raise StopIteration怎么回事?

>>> def foo(): raise StopIteration
>>> list(foo() for x in range(10))
[]
Run Code Online (Sandbox Code Playgroud)

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

这也意味着这样的代码:

def a_generator():
     yield 5
     raise StopIteration
Run Code Online (Sandbox Code Playgroud)

也将失败,并且邮件列表线程给人的印象是,无论如何这都被认为是错误的形式。正确的方法是:

def a_generator():
    yield 5
    return
Run Code Online (Sandbox Code Playgroud)

正如您所指出的,列表推导的行为已经有所不同:

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

这在某种程度上讲是一个实现细节泄漏-列表推导不会转换为list对等的生成器表达式的调用,并且显然这样做会导致巨大的性能损失,而这种能力被认为是禁止的。