【问题标题】:next() doesn't play nice with any/all in pythonnext() 在 python 中的任何/全部都不能很好地发挥作用
【发布时间】:2015-04-02 01:20:50
【问题描述】:

我今天遇到了一个错误,因为我使用next() 来提取一个值,而“未找到”会发出一个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

【问题讨论】:

  • @tdelaney 因为any 返回True 如果可迭代包含任何值Trueall 返回 True 如果迭代不包含 False 的值。
  • 所以...,集合中的所有项目都是真实的(因为没有任何项目),但没有一个项目是真实的(因为没有任何项目)。
  • next((i for i in range(3) if i==10), None) 将返回 None 而不是引发 StopIteration 异常。
  • @FrédéricHamidi - 嗯,我就是这么说的。

标签: python python-3.x generator generator-expression


【解决方案1】:

虽然这是 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))

【讨论】:

  • 谢谢 -- 我的手指交叉被 StopIteration 冒泡并没有让我发疯感到惊讶
  • 这是否意味着all(some_iterator)mlist = [i for i in some_iterator];all(mlist) 的行为会有所不同?
  • @tdelaney 是的,如果 some_iterator 是引发 StopIteration 的生成器。
【解决方案2】:

问题不在于使用all,而在于您有一个生成器表达式作为all 的参数。 StopIteration 被传播到生成器表达式,生成器表达式并不知道它的来源,所以它照常执行并结束迭代。

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

def error2(): raise StopIteration

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

难题的最后一部分是知道all 对空序列做了什么:

>>> all([])
True

如果你要直接使用next,你应该做好自己捕捉StopIteration的准备。

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

【讨论】:

  • 好的 - 所以你说的解决方案是不要使用 any/all 围绕任何可以引发 StopIteration 的东西?
  • @PadraicCunningham 它被生成器表达式本身捕获。
  • OP 可以捕获异常,也可以使用默认值next
  • @amwinter 不仅如此 - 建议不要调用任何可能从生成器表达式中引发 StopIteration 的内容。
  • 不知道为什么这被否决 - 这是对当前事态的一个很好的解释。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-02
相关资源
最近更新 更多