next() 在 python 中与 any/all 不兼容

next() doesn't play nice with any/all in python

我今天 运行 解决了一个错误,因为我使用 next() 提取值,并且 'not found' 发出 StopIteration.

通常这会暂停程序,但是使用 next 的函数在 all() 迭代中被调用,所以 all 提前终止并返回 True .

这是预期的行为吗?是否有有助于避免此类事情的风格指南?

简化示例:

def error(): return next(i for i in range(3) if i==10)
error() # fails with StopIteration
all(error() for i in range(2)) # returns True

问题不在于使用 all,而是你有一个生成器表达式作为 all 的参数。 StopIteration 被传播到生成器表达式,生成器表达式并不知道它起源于何处,因此它执行通常的操作并结束迭代。

您可以通过将 error 函数替换为直接引发错误的函数来看到这一点:

def error2(): raise StopIteration

>>> all(error2() for i in range(2))
True

拼图的最后一块是了解 all 对空序列的作用:

>>> all([])
True

如果你打算直接使用next,你应该准备好自己抓住StopIteration

编辑:很高兴看到 Python 开发人员认为这是一个错误,并正在采取措施在 3.7 中对其进行更改。

虽然这是 Python 3.6 及以下版本的默认行为,但它被认为是语言错误,并计划在 Python 3.7 中进行更改,以便出现异常反而被提高了。

正如PEP 479所说:

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.

从 Python 3.5 开始,可以将默认行为更改为 3.7 计划的行为。此代码:

# gs_exc.py

from __future__ import generator_stop

def error():
    return next(i for i in range(3) if i==10)

all(error() for i in range(2))

…引发以下异常:

Traceback (most recent call last):
  File "gs_exc.py", line 8, in <genexpr>
    all(error() for i in range(2))
  File "gs_exc.py", line 6, in error
    return next(i for i in range(3) if i==10)
StopIteration

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "gs_exc.py", line 8, in <module>
    all(error() for i in range(2))
RuntimeError: generator raised StopIteration

在 Python 3.5 和 3.6 没有 __future__ 导入中,会发出警告。例如:

# gs_warn.py

def error():
    return next(i for i in range(3) if i==10)

all(error() for i in range(2))

$ python3.5 -Wd gs_warn.py 
gs_warn.py:6: PendingDeprecationWarning: generator '<genexpr>' raised StopIteration
  all(error() for i in range(2))

$ python3.6 -Wd gs_warn.py 
gs_warn.py:6: DeprecationWarning: generator '<genexpr>' raised StopIteration
  all(error() for i in range(2))