Python: 捕获异常然后再抛出另一个异常的正确姿势
来源:互联网 发布:淘宝直播在哪里可以看 编辑:程序博客网 时间:2024/06/09 23:56
转载:https://mozillazg.github.io/2016/08/python-the-right-way-to-catch-exception-then-reraise-another-exception.html
一般大家实现捕获异常然后再抛出另一个异常的方法是下面这样的:
def div(): 2 / 0try: div()except ZeroDivisionError as e: raise ValueError(e)
不知道大家有没有注意到这样抛出异常的方式有一个很严重的问题,那就是 在重新抛出另一个异常的时候,捕获的上一个异常的 traceback 信息丢失了(python2):
$ cat a.pydef div(): 2 / 0try: div()except ZeroDivisionError as e: raise ValueError(e)$ python2 a.pyTraceback (most recent call last): File "a.py", line 6, in <module> raise ValueError(e)ValueError: integer division or modulo by zero
这样的话非常不利于查找问题: 比如上面的例子中实际出错的代码是第二行,但是 当我们捕获了第一个异常然后再抛出一个自定义异常的时候, 实际出错位置的信息就丢失了。
python2
那么在 Python 2 下如果我们不想丢失捕获的异常的 traceback 信息的话,应该 怎样重新抛出异常呢?
有两种办法, 还是用上面的例子举例:
一种办法是直接 raise:
$ cat a.pydef div(): 2 / 0try: div()except ZeroDivisionError as e: raise$ python2 a.pyTraceback (most recent call last): File "a.py", line 4, in <module> div() File "a.py", line 2, in div 2 / 0ZeroDivisionError: integer division or modulo by zero
另一种办法就是 raise 另一个异常时指定上一个异常的 traceback 信息 (通过 sys.exc_info() 获取当前捕获的异常信息):
$ cat a.pyimport sysdef div(): 2 / 0try: div()except ZeroDivisionError as e: raise ValueError(e), None, sys.exc_info()[2]$ python2 a.pyTraceback (most recent call last): File "a.py", line 6, in <module> div() File "a.py", line 4, in div 2 / 0ValueError: integer division or modulo by zero
这个是 raise 的高级用法:
raise exception, value, traceback
- exception: 异常类实例/异常类
- value: 初始化异常类的参数值/异常类实例(使用这个实例作为 - - raise 的异常实例)/元组/None
- traceback: traceback 对象/None
下面我们来看看上面的方法是否可以应对多层异常捕获然后再抛出的情况:
$ cat a.pyimport sysdef div(): 2 / 0def 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: raisefoobar()$ python2 a.pyTraceback (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 / 0TypeError: integer division or modulo by zero
从上面的结果可以看到这两种方法是支持多层异常 traceback 信息传递的。
那么在 Python 3 下又怎么解决这个问题呢?
Python 3
在 Python 3 下默认会附加上捕获的上个异常的 trackback 信息(保存在异常实例的 traceback 属性中):
$ cat a.pydef div(): 2 / 0try: div()except ZeroDivisionError as e: raise ValueError(e)$ python3 a.pyTraceback (most recent call last): File "a.py", line 4, in <module> div() File "a.py", line 2, in div 2 / 0ZeroDivisionError: division by zeroDuring 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.pydef div(): 2 / 0try: div()except ZeroDivisionError as e: raise ValueError(e) from e$ python a.pyTraceback (most recent call last): File "a.py", line 5, in <module> div() File "a.py", line 2, in div 2 / 0ZeroDivisionError: division by zeroThe 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 eValueError: division by zero
也可以指定使用的 traceback 对象: raise exception.with_traceback(traceback)
$ cat a.pyimport sysdef div(): 2 / 0try: div()except ZeroDivisionError as e: raise ValueError(e).with_traceback(sys.exc_info()[2])$ python a.pyTraceback (most recent call last): File "a.py", line 7, in <module> div() File "a.py", line 4, in div 2 / 0ZeroDivisionError: division by zeroDuring 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 / 0ValueError: division by zero
兼容 Python 2 和 Python 3 的写法
上面介绍了在 Python 2 和 Python 3 下的不同解决办法,那么如何写一个兼容 Python 2 和 Python 3 的 reraise 函数呢?
下面将介绍一种方法:
PY3 = sys.version_info[0] == 3if PY3: def reraise(tp, value, tb=None): if value.__traceback__ is not tb: raise value.with_traceback(tb) else: raise valueelse: 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.pyef div(): 2 / 0def 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: raisefoobar()
Python 2:
$ python2 a.pyTraceback (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 / 0TypeError: integer division or modulo by zeroPython 3:
$ python3 a.pyTraceback (most recent call last): File "a.py", line 19, in foo div() File "a.py", line 15, in div 2 / 0ZeroDivisionError: division by zeroDuring 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 / 0ValueError: division by zeroDuring 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 / 0TypeError: division by zero
下次需要捕获一个异常然后再抛出另一个异常的时候大家可以试试本文的方法。
参考资料
- Simple statements — Python 2.7.12 documentation
- Built-in Exceptions — Python 2.7.12 documentation
- Simple statements — Python 3.5.2 documentation
- Built-in Exceptions — Python 3.5.2 documentation
- PEP 3109 – Raising Exceptions in Python 3000 | Python.org
- bottle/bottle.py at cafc15419cbb4a6cb748e6ecdccf92893bb25ce5 · bottlepy/bottle
- flask/_compat.py at 6e46d0cd3969f6c13ff61c95c81a975192232fed · pallets/flask
- Python: 捕获异常然后再抛出另一个异常的正确姿势
- 捕获异常然后抛出另一个异常发生了什么?
- 页面捕获抛出的异常
- 抛出异常和捕获异常的区别
- 抛出异常和捕获异常的区别
- 抛异常的正确姿势
- Python 自定义异常类,主动捕获异常,主动抛出异常
- 抛出异常和捕获异常
- java 捕获线程里抛出的异常
- java异常的捕获与抛出原则
- Java异常的捕获和抛出
- java异常的捕获与抛出原则
- runnable的线程抛出异常无法捕获
- Java- 异常的抛出与捕获
- java异常的捕获与抛出原则
- java异常的捕获与抛出原则
- C++的异常抛出与捕获
- dubbo捕获提供者抛出的自定义异常
- C++函数副本机制研究&函数返回值与拷贝构造的浅拷贝和深拷贝的关系
- js导出页面内某一标签内容
- ES6学习——新的语法:对象字面量扩展(Object Literal Extensions)
- 百度图片API接口
- 弗洛伊德算法求出最短路径
- Python: 捕获异常然后再抛出另一个异常的正确姿势
- springboot配置跳转html页面
- MFC子窗口向父窗口发送消息(测试成功20110117)
- 用Caffe自制训练数据库报错 E0407 error
- 【C++ Primer学习笔记2】string类
- mysql自动备份数据库并发送邮件
- 100多个基础常用JS函数和语法集合大全
- 创业
- Django<v_11.1>报错:ImportError: No module named urls