【问题标题】:Why do these generator expressions behave differently?为什么这些生成器表达式的行为不同?
【发布时间】:2015-09-30 09:47:16
【问题描述】:

这两个代码片段仅在构造列表的方式上有所不同。一个使用[],另一个使用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.

这种行为的规则是什么?

【问题讨论】:

标签: python list-comprehension generator-expression


【解决方案1】:

参考PEP479,上面写着

generators 和 StopIteration 的交互目前有点 令人惊讶,并且可以隐藏晦涩的错误。意外的异常 不应导致细微的行为改变,但应引起 嘈杂且易于调试的回溯。 目前,提出了 StopIteration 意外在生成器函数中将被解释为 循环构造驱动生成器的迭代结束

(强调我的)

所以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提出的行为如下

如果StopIteration 即将从生成器框架中冒出,它是 替换为RuntimeError,这会导致next() 调用(其中 调用生成器)失败,将异常传递出去。从那时起 就像任何旧的例外一样。

Python 3.5 添加了generator_stop feature,可以使用

from __future__ import generator_stop

此行为将在 Python 3.7 中成为默认行为。

【讨论】:

  • 这种行为的规则是什么?为什么[] 不抑制StopIteration
  • @Yurim 我没有任何参考资料,但我很确定这个异常不会在list() 中存活下来。在内部,它引发异常并导致输出为 nothing,然后 list 会选择该 nothing 并生成一个空列表。因为列表推导也在list()中寻找同样的异常@
猜你喜欢
  • 2020-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多