2010-10-22 51 views
12

我最近遇到了一些令人吃驚的行爲排在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不會吞下異常的實例嗎?

回答

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 

由於這仍然得到upvotes,這裏是你如何在一臺發電機產生一個異常:

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

+0

啊哈,現在有道理。我最初並沒有預料到這個例外會傳播給發電機。 – EoghanM 2010-10-22 14:22:50

+0

+1非常有趣! – rubik 2010-10-22 14:41:01

+0

用於照亮'generator.throw'技巧! – EoghanM 2013-02-27 12:03:53

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循環吞噬,但在這種情況下,我們使發生器產生異常,因此循環不會注意到它們。