通常实现捕获异常而后再抛出另外一个异常的方法相似下面这样: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
下次须要捕获一个异常而后再抛出另外一个异常的时候你们能够试试本文的方法。