26

我今天遇到了一个错误,该错误是因为我next()用来提取一个值,而“未找到”会发出一个StopIteration.

通常这会停止程序,但函数 using在迭代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
4

2 回答 2

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))
于 2015-02-03T00:17:46.017 回答
9

问题不在于 using all,而是您有一个生成器表达式作为all. 被StopIteration传播到生成器表达式,它并不真正知道它的起源,所以它做通常的事情并结束迭代。

error你可以通过用直接引发错误的东西替换你的函数来看到这一点:

def error2(): raise StopIteration

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

难题的最后一部分是知道如何all处理空序列:

>>> all([])
True

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

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

于 2015-02-02T23:52:14.943 回答