为什么这些生成器表达式的行为不同?

Why do these generator expressions behave differently?

这两个代码片段仅在构建列表的方式上有所不同。一个使用 [],另一个使用 list().

这个消耗了可迭代对象然后引发了一个 StopIteration:

>>> try:
...     iterable = iter(range(4))
...     while True:
...         print([next(iterable) for _ in range(2)])
... except StopIteration:
...     pass
...
[0, 1]
[2, 3]

这个消耗可迭代对象并永远循环打印空列表。

>>> try:
...     iterable = iter(range(4))
...     while True:
...         print(list(next(iterable) for _ in range(2)))
... except StopIteration:
...     pass
...
[0, 1]
[2, 3]
[]
[]
[]
etc.

这种行为的规则是什么?

参考PEP479,里面说

The interaction of generators and StopIteration is currently somewhat surprising, and can conceal obscure bugs. An unexpected exception should not result in subtly altered behaviour, but should cause a noisy and easily-debugged traceback. Currently, StopIteration raised accidentally inside a generator function will be interpreted as the end of the iteration by the loop construct driving the generator.

(强调我的)

因此 list 的构造函数遍历传递的生成器表达式,直到引发 StopIteration 错误(通过调用 next(iterable) 而不带第二个参数)。另一个例子:

def f():
    raise StopIteration # explicitly

def g():
    return 'g'

print(list(x() for x in (g, f, g))) # ['g']
print([x() for x in (g, f, g)]) # `f` raises StopIteration

另一方面,*理解在将 StopIteration 传播给调用者时工作方式不同。


链接的PEP提出的行为如下

If a StopIteration is about to bubble out of a generator frame, it is replaced with RuntimeError, which causes the next() call (which invoked the generator) to fail, passing that exception out. From then on it's just like any old exception.

Python 3.5 添加了可以使用

启用的 generator_stop feature
from __future__ import generator_stop

此行为将在 Python 3.7.

中成为默认行为