受到my own answer的启发,我甚至不了解它是如何运作的,请考虑以下因素:
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
。
>>> 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)
[]
所以我想我的问题是,为什么这个行为启用了生成器表达式?
注意: 3.x
中的相同行为
答案 0 :(得分:5)
开发人员已经决定允许这是一个错误,因为它可以掩盖晦涩的错误。因此,接受PEP 479意味着这种情况正在消失。
在Python 3.5中如果执行from __future__ import generator_stop
,默认情况下在Python 3.7中,问题中的示例将失败并显示RuntimeError
。你仍然可以通过一些itertools魔法达到同样的效果(允许nums
不被预先计算):
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 a in b:
# do stuff
与(大致)相当于:
b = iter(b)
while True:
try:
a = next(b)
except StopIteration:
break
else:
# do stuff
现在,所有示例都有两个 for循环嵌套在一起(一个在生成器表达式中,一个在函数中使用它),因此当外循环执行它时,内循环迭代一次{ {1}}致电。当内循环中的'#do stuff'为next
时会发生什么?
raise StopIteration
异常传播出内部循环,因为它不在其保护范围内,并被外部循环捕获。在新行为下,Python将拦截即将从生成器传播的>>> def foo(): raise StopIteration
>>> list(foo() for x in range(10))
[]
并将其替换为StopIteration
,将不会被包含的捕获for循环。
这也暗示了这样的代码:
RuntimeError
也会失败,邮件列表主题给人的印象是,无论如何这被视为不良形式。正确的方法是:
def a_generator():
yield 5
raise StopIteration
正如您所指出的,列表推导已经表现得不同:
def a_generator():
yield 5
return
这有点是一个实现细节泄漏 - 列表推导没有转换为对具有等效生成器表达式的>>> [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
的调用,并且显然这样做would cause large performance penalties被认为是禁止的权力。
答案 1 :(得分:4)
有趣的行为,但绝对可以理解。
如果将生成器表达式转换为生成器:
def _has22_iter(it):
for x in it:
yield x == 2 and x == next(it)
def has22(nums):
it = iter(nums)
return any(_has22_iter(it))
您的生成器在以下条件中引发StopIteration
:
return
语句raise StopIteration
这里,你有第三个条件,所以生成器终止了。
与以下内容比较:
def testgen(x):
if x == 0:
next(iter([])) # implicitly raise
if x == 1:
raise StopIteration
if x == 2:
return
并做
list(testgen(0)) # --> []
list(testgen(1)) # --> []
list(testgen(2)) # --> []
list(testgen(3)) # --> []
在所有情况下都会得到相同的行为。