捕獲異常而後再拋出另外一個異常的正確姿式

通常實現捕獲異常而後再拋出另外一個異常的方法相似下面這樣: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

那麼在 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

在 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 下的不一樣解決辦法,那麼如何寫一個兼容 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

下次須要捕獲一個異常而後再拋出另外一個異常的時候你們能夠試試本文的方法。

參考資料

原文地址: https://mozillazg.com/2016/08...

相關文章
相關標籤/搜索