通常實現捕獲異常而後再拋出另外一個異常的方法相似下面這樣:html
def div(): 2 / 0 try: div() except ZeroDivisionError as e: raise ValueError(e)
不知道你們有沒有注意到這樣拋出異常的方式有一個很嚴重的問題,那就是 在從新拋出另外一個異常的時候,捕獲的上一個異常的 traceback 信息丟失了(python2): :python
$ cat a.py def div(): 2 / 0 try: div() except ZeroDivisionError as e: raise ValueError(e) $ python2 a.py Traceback (most recent call last): File "a.py", line 6, in <module> raise ValueError(e) ValueError: integer division or modulo by zero
這樣的話很是不利於查找問題: 好比上面的例子中實際出錯的代碼是第二行,可是 當咱們捕獲了第一個異常而後再拋出一個自定義異常的時候, 實際出錯位置的信息就丟失了。git
那麼在 Python 2 下若是咱們不想丟失捕獲的異常的 traceback 信息的話,應該 怎樣從新拋出異常呢?github
有兩種辦法, 仍是用上面的例子舉例:flask
一種辦法是直接 raise
: :函數
$ cat a.py def div(): 2 / 0 try: div() except ZeroDivisionError as e: raise $ python2 a.py Traceback (most recent call last): File "a.py", line 4, in <module> div() File "a.py", line 2, in div 2 / 0 ZeroDivisionError: integer division or modulo by zero
另外一種辦法就是 raise
另外一個異常時指定上一個異常的 traceback 信息 (經過 sys.exc_info()
獲取當前捕獲的異常信息): :ui
$ cat a.py import sys def div(): 2 / 0 try: div() except ZeroDivisionError as e: raise ValueError(e), None, sys.exc_info()[2] $ python2 a.py Traceback (most recent call last): File "a.py", line 6, in <module> div() File "a.py", line 4, in div 2 / 0 ValueError: integer division or modulo by zero
這個是 raise
的高級用法:code
raise exception, value, traceback
exception
: 異常類實例/異常類htm
value
: 初始化異常類的參數值/異常類實例(使用這個實例做爲 raise 的異常實例)/元組/None對象
traceback
: traceback 對象/None
下面咱們來看看上面的方法是否能夠應對多層異常捕獲而後再拋出的狀況: :
$ cat a.py import sys def div(): 2 / 0 def foo(): try: div() except ZeroDivisionError as e: raise ValueError(e), None, sys.exc_info()[2] def bar(): try: foo() except ValueError as e: raise TypeError(e), None, sys.exc_info()[2] def foobar(): try: bar() except TypeError as e: raise foobar() $ python2 a.py Traceback (most recent call last): File "a.py", line 23, in <module> foobar() File "a.py", line 20, in foobar bar() File "a.py", line 14, in bar foo() File "a.py", line 8, in foo div() File "a.py", line 4, in div 2 / 0 TypeError: integer division or modulo by zero
從上面的結果能夠看到這兩種方法是支持多層異常 traceback 信息傳遞的。
那麼在 Python 3 下又怎麼解決這個問題呢?
在 Python 3 下默認會附加上捕獲的上個異常的 trackback 信息(保存在異常實例的 __traceback__
屬性中): :
$ cat a.py def div(): 2 / 0 try: div() except ZeroDivisionError as e: raise ValueError(e) $ python3 a.py Traceback (most recent call last): File "a.py", line 4, in <module> div() File "a.py", line 2, in div 2 / 0 ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File "a.py", line 6, in <module> raise ValueError(e) ValueError: division by zero
也支持指定使用哪一個異常實例的 traceback 信息: raise ... from ...
:
$ cat a.py def div(): 2 / 0 try: div() except ZeroDivisionError as e: raise ValueError(e) from e $ python a.py Traceback (most recent call last): File "a.py", line 5, in <module> div() File "a.py", line 2, in div 2 / 0 ZeroDivisionError: division by zero The above exception was the direct cause of the following exception: Traceback (most recent call last): File "a.py", line 7, in <module> raise ValueError(e) from e ValueError: division by zero
也能夠指定使用的 traceback 對象: raise exception.with_traceback(traceback)
:
$ cat a.py import sys def div(): 2 / 0 try: div() except ZeroDivisionError as e: raise ValueError(e).with_traceback(sys.exc_info()[2]) $ python a.py Traceback (most recent call last): File "a.py", line 7, in <module> div() File "a.py", line 4, in div 2 / 0 ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File "a.py", line 9, in <module> raise ValueError(e).with_traceback(sys.exc_info()[2]) File "a.py", line 7, in <module> div() File "a.py", line 4, in div 2 / 0 ValueError: division by zero
上面介紹了在 Python 2 和 Python 3 下的不一樣解決辦法,那麼如何寫一個兼容 Python 2 和 Python 3 的 reraise
函數呢?
下面將介紹一種方法:
PY3 = sys.version_info[0] == 3 if PY3: def reraise(tp, value, tb=None): if value.__traceback__ is not tb: raise value.with_traceback(tb) else: raise value else: exec('''def reraise(tp, value, tb=None): raise tp, value, tb ''')
這裏的 reraise
函數咱們約定了 vlaue
參數的值是一個異常類的實例。 上面 else
中之因此用 exec
去定義 reraise
函數是由於 raise tp, value, tb
在 Python 3 下會報語法錯誤,因此用 exec
來 繞過 Python 3 下的語法錯誤檢查。
下面咱們來看一下效果: :
$ cat a.py ef div(): 2 / 0 def foo(): try: div() except ZeroDivisionError as e: reraise(ValueError, ValueError(e), sys.exc_info()[2]) def bar(): try: foo() except ValueError as e: reraise(TypeError, TypeError(e), sys.exc_info()[2]) def foobar(): try: bar() except TypeError: raise foobar()
Python 2: :
$ python2 a.py Traceback (most recent call last): File "a.py", line 34, in <module> foobar() File "a.py", line 31, in foobar bar() File "a.py", line 27, in bar reraise(TypeError, TypeError(e), sys.exc_info()[2]) File "a.py", line 25, in bar foo() File "a.py", line 21, in foo reraise(ValueError, ValueError(e), sys.exc_info()[2]) File "a.py", line 19, in foo div() File "a.py", line 15, in div 2 / 0 TypeError: integer division or modulo by zero
Python 3: :
$ python3 a.py Traceback (most recent call last): File "a.py", line 19, in foo div() File "a.py", line 15, in div 2 / 0 ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File "a.py", line 25, in bar foo() File "a.py", line 21, in foo reraise(ValueError, ValueError(e), sys.exc_info()[2]) File "a.py", line 6, in reraise raise value.with_traceback(tb) File "a.py", line 19, in foo div() File "a.py", line 15, in div 2 / 0 ValueError: division by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File "a.py", line 34, in <module> foobar() File "a.py", line 31, in foobar bar() File "a.py", line 27, in bar reraise(TypeError, TypeError(e), sys.exc_info()[2]) File "a.py", line 6, in reraise raise value.with_traceback(tb) File "a.py", line 25, in bar foo() File "a.py", line 21, in foo reraise(ValueError, ValueError(e), sys.exc_info()[2]) File "a.py", line 6, in reraise raise value.with_traceback(tb) File "a.py", line 19, in foo div() File "a.py", line 15, in div 2 / 0 TypeError: division by zero
下次須要捕獲一個異常而後再拋出另外一個異常的時候你們能夠試試本文的方法。