文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

捕获异常然后再抛出另一个异常的正确姿势

2023-01-31 08:18

关注

一般实现捕获异常然后再抛出另一个异常的方法类似下面这样:

def div():
    2 / 0

try:
    div()
except ZeroDivisionError as e:
    raise ValueError(e)

不知道大家有没有注意到这样抛出异常的方式有一个很严重的问题,那就是 在重新抛出另一个异常的时候,捕获的上一个异常的 traceback 信息丢失了(python2): :

$ 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

这样的话非常不利于查找问题: 比如上面的例子中实际出错的代码是第二行,但是 当我们捕获了第一个异常然后再抛出一个自定义异常的时候, 实际出错位置的信息就丢失了。

Python 2

那么在 Python 2 下如果我们不想丢失捕获的异常的 traceback 信息的话,应该 怎样重新抛出异常呢?

有两种办法, 还是用上面的例子举例:

一种办法是直接 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() 获取当前捕获的异常信息): :

$ 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 的高级用法:

raise exception, value, traceback
  • exception: 异常类实例/异常类

  • 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

下次需要捕获一个异常然后再抛出另一个异常的时候大家可以试试本文的方法。

参考资料

  • 6. Simple statements — Python 2.7.12 documentation

  • 6. Built-in Exceptions — Python 2.7.12 documentation

  • 7. Simple statements — Python 3.5.2 documentation

  • 5. 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

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

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯