资讯专栏INFORMATION COLUMN

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

RebeccaZhong / 830人阅读

摘要:下面我们来看一下效果下次需要捕获一个异常然后再抛出另一个异常的时候大家可以试试本文的方法。

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

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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    raise ValueError(e).with_traceback(sys.exc_info()[2])
  File "a.py", line 7, in 
    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 
    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 
    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...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/38180.html

相关文章

  • 处理JavaScript异常正确姿势

    摘要:我们使用单元测试来验证一下我们使用了配合做单元测试。我们编写相应的单元测试你会发现,如果出现异常,只是简单的返回。但是在上面异常抛出的时候,解释器已经不在中了,因此无法被捕获。 译者按: 错误是无法避免的,妥善处理它才是最重要的! 原文: A Guide to Proper Error Handling in JavaScript Related Topics: 译者: Funde...

    lushan 评论0 收藏0
  • 《Java编程思想》笔记12.通过异常处理错误

    摘要:一旦异常被抛出,就表明错误已无法挽回,也不能回来继续执行。这种在编译时被强制检查的异常称为被检查的异常。通过获取原始异常。构造器对于在构造阶段可能会抛出异常,并要求清理的类,最安全的做法是使用嵌套的子句。 点击进入我的博客 Java异常处理的目的在于通过使用少于目前数量的代码来简化大型、可靠的程序的生成,并且通过这种方式可以使你更自信:你的应用中没有未处理的错误。 12.1 概念 异...

    Vultr 评论0 收藏0
  • 使用Java Exception机制正确姿势

    摘要:如何良好的在代码中设计异常机制本身设计的出发点是极好的,通过编译器的强制捕获,可以明确提醒调用者处理异常情况。但使用此种异常后,该会像病毒一样,得不到处理后会污染大量代码,同时也可能因为调用者的不当处理,会失去异常信息。 1、异常是什么? 父类为Throwable,有Error和Exception两个子类 Error为系统级别的异常(错误) Exception下有众多子类,常见的有Ru...

    Astrian 评论0 收藏0
  • Java异常简介

    摘要:而异常可以不被显式的处理都是的子类,继承了的就是异常,其他的就是异常。常见异常类列举几个常见的运行时异常数组越界异常空指针异常类转换异常数字格式异常运算异常。 Java异常 java异常分为两大类,Checked异常和Runtime异常,Checked异常都是在编译阶段可以被处理的异常。 Checked异常和Runtime异常的区别和联系 Checked异常都是可以被处理的异常,在程...

    wangym 评论0 收藏0
  • Java中异常处理

    摘要:对异常的处理方法是打印异常的跟踪栈信息并终止程序运行。应尽量对异常进行适当的处理,而不是简单的将异常跟踪栈信息打印出来。 一、异常概述 开发者都希望所有错误都能在编译阶段被发现,就是试图在运行程序之前排除所有错误,但这是不现实的,余下问题必须在运行期间得到解决。 Java将异常分为两种:CheckedException和RuntimeException。其中,CheckedExcept...

    wemall 评论0 收藏0

发表评论

0条评论

RebeccaZhong

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<