避免在Python增强生成器中出现"exception ignored"

9

我有一个协程(增强生成器)在Python中,其中包含一些在数据结束后执行的代码:

def mycoroutine():
  try:
    while True:
      data = (yield)
      print data
  finally:
    raise ValueError
    print "END"

co = mycoroutine()
co.next()

for i in (1,2,3):
  co.send(i)

“ValueError”异常不会被触发,但解释器会简单地打印出:
Exception ValueError: ValueError() in <generator object mycoroutine at 0x2b59dfa23d20> ignored

有没有一种方法可以在调用者中捕获异常?
1个回答

14

抛出异常。当生成器关闭时,将执行finally块。通过在生成器上下文中引发GeneratorExit异常来关闭生成器。

由于生成器直到被删除时才关闭(在此情况下自动在Python退出时),因此忽略了异常;生成器的__del__处理程序关闭生成器,从而触发finally:块:

>>> def mycoroutine():
...   try:
...     while True:
...       data = (yield)
...       print data
...   finally:
...     raise ValueError
...     print "END"
... 
>>> co = mycoroutine()
>>> co.next()
>>> co.close()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in mycoroutine
ValueError
>>> co = mycoroutine()
>>> co.next()
>>> del co
Exception ValueError: ValueError() in <generator object mycoroutine at 0x1046a9fa0> ignored

在清理期间引发的异常总是被忽略的;请参阅object.__del__()文档

警告: 由于调用__del__()方法时的不稳定情况,其执行期间发生的异常将被忽略,并在sys.stderr上打印警告。

解决方案是在生成器清理时不要引发异常,或通过显式关闭生成器来捕获异常:
>>> co = mycoroutine()
>>> co.next()
>>> try:
...     co.close()
... except ValueError:
...     pass
... 
>>> del co
>>> # No exception was raised
... 

您还可以捕获 GeneratorExit 异常并在那时执行一些清理工作:
def mycoroutine():
  try:
    while True:
      data = (yield)
      print data
  except GeneratorExit:
    print "Generator exiting!"

请注意,除了 StopIterationGeneratorExit 之外的任何异常都将被传播;请参阅 generator.close() 文档
如果生成器函数然后引发 StopIteration(通过正常退出或由于已经关闭)或 GeneratorExit(未捕获异常),则 close 返回到其调用者。如果生成器产生值,则会引发 RuntimeError。如果生成器引发任何其他异常,则将其传播到调用者。

如果我用except GeneratorExit:替换finally,同样的事情也会发生。我想这是一样的... - Zac
@Zac:当然会,你仍然会raise一个ValueError,不是吗?捕获GeneratorExit并引发另一个异常并不能阻止该异常传播。 - Martijn Pieters
@WalterNissen:为什么这很奇怪?当解释器退出时,就没有堆栈可以跟踪了。 - Martijn Pieters
因为不可能找到哪里产生了被忽略的异常。它没有列出行甚至类,只有异常。我们有75K行Python代码,追踪那个执行导入操作并因此导致“ImportError”的 __del__ 不是一件容易的事情。 - Walter Nissen
@WalterNissen 对的,但是没有回溯是因为没有堆栈。在那里没有别的可说的了。确保你的类有良好的__repr__方法,在写入到stderr的Exception ... in [object_repr] ignored错误中识别它们。 - Martijn Pieters
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接