Python生成器,协程中的非吞没异常
我最近在使用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
的文档了解更多。