我今天遇到了一个错误,因为我使用next()
来提取值,而“找不到”会发出StopIteration
。
通常会暂停程序,但在next
次迭代中调用使用all()
的函数,因此all
提前终止并返回True
。< / p>
这是预期的行为吗?是否有风格指南,以帮助避免这种事情?
简化示例:
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
答案 0 :(得分:24)
虽然这是Python版本(包括3.6版本)的默认行为,但它被认为是该语言的错误,并且计划在Python 3.7中进行更改,以便引发异常。
正如PEP 479所说:
生成器和
StopIteration
的交互目前有点令人惊讶,并且可以隐藏不明显的错误。意外的异常不应导致轻微改变的行为,但应导致嘈杂且易于调试的回溯。目前,在生成器函数内意外引发的StopIteration
将被解释为循环结构驱动生成器的迭代结束。
从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))
答案 1 :(得分:9)
问题不在于使用all
,而是您将生成器表达式作为all
的参数。 StopIteration
传播到生成器表达式,它实际上并不知道它的来源,所以它做了常用的事情并结束了迭代。
您可以通过将error
函数替换为直接引发错误的内容来查看此内容:
def error2(): raise StopIteration
>>> all(error2() for i in range(2))
True
最后一个难题是知道all
对空序列的作用:
>>> all([])
True
如果您要直接使用next
,您应该准备好自己抓住StopIteration
。
编辑:很高兴看到Python开发人员认为这是一个错误,并且正在采取措施在3.7中更改它。