Python生成器,协程中的非吞没异常

12 投票
2 回答
1621 浏览
提问于 2025-04-16 05:55

我最近在使用Python的生成器时,发现了一些意想不到的行为:

class YieldOne:
  def __iter__(self):
    try:
      yield 1
    except:
      print '*Excepted Successfully*'
      # raise

for i in YieldOne():
  raise Exception('test exception')

这个代码的输出是:

*Excepted Successfully*
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
Exception: test exception

我感到(很高兴)惊讶的是,*Excepted Successfully* 被打印出来了,因为这是我想要的结果,但我也很惊讶的是,异常还是传递到了最上层。我原本以为需要使用(在这个例子中被注释掉的)raise 关键字才能得到这样的效果。

有没有人能解释一下为什么会有这样的功能,以及为什么生成器中的except没有吞掉这个异常呢?

在Python中,是否只有这一种情况是except没有吞掉异常的呢?

2 个回答

6

编辑:THC4k说的没错。

如果你想在一个生成器里面抛出一个任意的异常,可以使用 throw 方法:

>>> def Gen():
...     try:
...             yield 1
...     except Exception:
...             print "Excepted."
...
>>> foo = Gen()
>>> next(foo)
1
>>> foo.throw(Exception())
Excepted.
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

你会发现,在最外层会出现一个 StopIteration 的错误。这种错误是由生成器在没有更多元素时抛出的;通常情况下,这种错误会被 for 循环忽略掉,但在这个例子中,我们让生成器抛出了一个异常,所以循环没有注意到这个错误。

14

你的代码并没有按照你想的那样工作。在协程里,你不能像这样抛出异常。你应该做的是捕获 GeneratorExit 这个异常。看看当你使用其他异常时会发生什么:

class YieldOne:
  def __iter__(self):
    try:
      yield 1
    except RuntimeError:
        print "you won't see this"
    except GeneratorExit:
      print 'this is what you saw before'
      # raise

for i in YieldOne():
  raise RuntimeError

因为这个问题仍然有人点赞,所以这里教你如何在生成器中抛出异常:

class YieldOne:
  def __iter__(self):
    try:
      yield 1
    except Exception as e:
      print "Got a", repr(e)
      yield 2
      # raise

gen = iter(YieldOne())

for row in gen:
    print row # we are at `yield 1`
    print gen.throw(Exception) # throw there and go to `yield 2` 

可以查看 generator.throw 的文档了解更多。

撰写回答