2012-01-25 54 views
19

我正在寫一個裝飾器應用於一個函數。它應該捕獲任何異常,然後根據原始異常消息引發自定義異常。 (這是因爲suds拋出了一個通用的WebFault異常,從其解析Web服務拋出的異常並引發一個Python異常來鏡像它)。Python:異常裝飾器。如何保存堆棧跟蹤

但是,當我在包裝器中引發自定義異常時,希望堆棧跟蹤指向引發原始WebFault異常的函數。我到目前爲止提出了正確的異常(它動態地解析消息並實例化異常類)。 我的問題:如何保留stacktrace以指向引發WebFault異常的原始函數?

from functools import wraps 

def try_except(fn): 
     def wrapped(*args, **kwargs): 
      try: 
       fn(*args, **kwargs) 
      except Exception, e: 
       parser = exceptions.ExceptionParser() 
       raised_exception = parser.get_raised_exception_class_name(e) 
       exception = getattr(exceptions, raised_exception) 
       raise exception(parser.get_message(e)) 
     return wraps(fn)(wrapped) 
+1

你看了'traceback'模塊? http://docs.python.org/library/traceback.html – stderr

+0

在包裝器中使用[functools.wrap](https://docs.python.org/2/library/functools.html) –

+0

可能的重複[「內部異常」(與追溯)在Python?](http://stackoverflow.com/questions/1350671/inner-exception-with-traceback-in-python) –

回答

37

在Python 2.x中,的raise一個鮮爲人知的特點是,它可以與多於只有一個參數被用於:的raise的三個參數的形式發生異常類型,異常實例和追溯。您可以通過sys.exc_info()獲取回溯,它返回(並非巧合)異常類型,異常實例和回溯。

(這會將異常類型和異常實例作爲兩個單獨的參數的原因是從天的異常類之前的僞影。)

所以:

import sys 

class MyError(Exception): 
    pass 

def try_except(fn): 
    def wrapped(*args, **kwargs): 
     try: 
      return fn(*args, **kwargs) 
     except Exception, e: 
      et, ei, tb = sys.exc_info() 
      raise MyError, MyError(e), tb 
    return wrapped 

def bottom(): 
    1/0 

@try_except 
def middle(): 
    bottom() 

def top(): 
    middle() 

>>> top() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "tmp.py", line 24, in top 
    middle() 
    File "tmp.py", line 10, in wrapped 
    return fn(*args, **kwargs) 
    File "tmp.py", line 21, in middle 
    bottom() 
    File "tmp.py", line 17, in bottom 
    1/0 
__main__.MyError: integer division or modulo by zero 

在Python 3,這改變了一點。在那裏,回溯連接到異常實例,而是和他們有一個with_traceback方法:

raise MyError(e).with_traceback(tb) 

在另一方面Python 3中也有例外,這使得在許多情況下更有意義;利用這一點,你就會只用:

raise MyError(e) from e 
+0

太好了,謝謝!這完成了這項工作。唯一的問題是,我必須將裝飾器的聲明移到與正在裝飾的函數相同的文件中,否則返回sys.exc_info()(None,None,None) - 關於爲什麼會這樣? – igniteflow

+0

......沒有意義。 sys.exc_info()不關心調用者的定義。它返回當前正在處理的異常。這聽起來像你在一個單獨的文件中的裝飾器沒有做正確的事情,但很難說沒有看到實際的代碼。 –

4

我面臨這個問題與被裝飾了我的自定義裝飾測試。

我用以下構建裝飾體內保留原始痕跡在單元測試輸出打印:

​​
+0

是的,但裝飾者的參數是什麼? –

+0

只需指出與上面的代碼的區別是'traceback.format_tb'。謝謝。 – Ehvince