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))
我今天 运行 解决了一个错误,因为我使用 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))