如果不立即重新引发,异常回溯将被隐藏

问题描述 投票:0回答:7

我有一段类似这样的代码:

import sys

def func1():
    func2()

def func2():
    raise Exception('test error')

def main():
    err = None

    try:
        func1()
    except:
        err = sys.exc_info()[1]
        pass

    # some extra processing, involving checking err details (if err is not None)

    # need to re-raise err so caller can do its own handling
    if err:
        raise err

if __name__ == '__main__':
    main()

func2
引发异常时,我收到以下回溯:

Traceback (most recent call last):
  File "err_test.py", line 25, in <module>
    main()
  File "err_test.py", line 22, in main
    raise err
Exception: test error

从这里我看不出异常来自哪里。原始回溯丢失了。

如何保留原始回溯并重新引发它?我想看到类似的东西:

Traceback (most recent call last):
  File "err_test.py", line 26, in <module>
    main()
  File "err_test.py", line 13, in main
    func1()
  File "err_test.py", line 4, in func1
    func2()
  File "err_test.py", line 7, in func2
    raise Exception('test error')
Exception: test error
python exception try-catch traceback
7个回答
128
投票

空白

raise
引发最后一个异常。

# need to re-raise err so caller can do its own handling
if err:
    raise

如果您使用

raise something
,Python 无法知道
something
是之前刚刚捕获的异常,还是具有新堆栈跟踪的新异常。这就是为什么有空白
raise
来保留堆栈跟踪。

参考这里


71
投票

可以修改并重新抛出异常:

如果不存在表达式,

raise
重新引发最后一个异常 在当前范围内处于活跃状态。如果没有异常处于活动状态 当前范围,会引发
TypeError
异常,表明这是 错误(如果在 IDLE 下运行,则会引发
Queue.Empty
异常 相反)。

否则,

raise
使用以下方法计算表达式以获取三个对象
None
作为省略表达式的值。前两个对象是 用于确定异常的类型和值。

如果存在第三个对象但不存在

None
,则它必须是回溯 对象(请参阅标准类型层次结构部分),它是 代替当前位置作为该位置 发生异常。如果第三个对象存在并且不是回溯 对象或
None
,会引发
TypeError
异常。

三表达

raise
的形式对于以透明方式重新引发异常很有用
except
子句,但不带表达式的
raise
应该是首选,如果 要重新引发的异常是最近活跃的异常 在当前范围内。

所以如果你想修改异常并重新抛出它,你可以这样做:

try:
    buggy_code_which_throws_exception()
except Exception as e:
    raise Exception, "The code is buggy: %s" % e, sys.exc_info()[2]

8
投票

您可以通过

sys.exc_info()
以及 traceback 模块

获取有关异常的大量信息

尝试对您的代码进行以下扩展。

import sys
import traceback

def func1():
    func2()

def func2():
    raise Exception('test error')

def main():

    try:
        func1()
    except:
        exc_type, exc_value, exc_traceback = sys.exc_info()
        # Do your verification using exc_value and exc_traceback

        print "*** print_exception:"
        traceback.print_exception(exc_type, exc_value, exc_traceback,
                                  limit=3, file=sys.stdout)

if __name__ == '__main__':
    main()

这将打印出来,与您想要的类似。

*** print_exception:
Traceback (most recent call last):
  File "err_test.py", line 14, in main
    func1()
  File "err_test.py", line 5, in func1
    func2()
  File "err_test.py", line 8, in func2
    raise Exception('test error')
Exception: test error

5
投票

虽然@Jochen的答案在简单的情况下效果很好,但它无法处理更复杂的情况,在这些情况下,您不能直接捕获和重新抛出,而是由于某种原因将异常作为对象给出并希望重新抛出全新的上下文(即,如果您需要在不同的进程中处理它)。

针对这种情况,我提出以下建议:

  1. 获取原始 exc_info
  2. 格式化原始错误消息,并带有堆栈跟踪
  3. 抛出一个新的异常,并嵌入完整的错误消息(包括堆栈跟踪)

在执行此操作之前,定义一个新的异常类型,稍后您将重新抛出该异常类型...

class ChildTaskException(Exception):
    pass

在有问题的代码中...

import sys
import traceback

try:
    # do something dangerous
except:
    error_type, error, tb = sys.exc_info()
    error_lines = traceback.format_exception(error_type, error, tb)
    error_msg = ''.join(error_lines)
    # for example, if you are doing multiprocessing, you might want to send this to another process via a pipe
    connection.send(error_msg)

重新抛出...

# again, a multiprocessing example of receiving that message through a pipe
error_msg = pcon.recv()
raise ChildTaskException(error_msg)

2
投票

您的主要功能需要如下所示:

def main():
    try:
        func1()
    except Exception, err:
        # error processing
        raise

这是处理(和重新引发)错误的标准方法。 这是键盘演示。


2
投票

在 Python 3 中:

import sys

class CustomError(Exception):
    pass

try:
    code_throwing_an_exception()
except Exception as e:
    _, value, traceback = sys.exc_info()
    raise CustomError("A new Exception was raised: %s" % value).with_traceback(traceback)

0
投票

正如 Jochen 提到的,Python 无法知道

err
是新异常还是捕获的正在重新引发的异常。
from
子句在这里可以提供帮助。 我有一个
test.py
,其中包含以下代码:

try:
    raise RuntimeError("Original error")
except RuntimeError as e:
    err = e

raise RuntimeError("Previous operation failed") from err

结果是合理的:

$ python test.py
Traceback (most recent call last):
  File "/home/valajeyhani/development/repositories/geotab/image_builder/test.py", line 2, in <module>
    raise RuntimeError("Original error")
RuntimeError: Original error

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/valajeyhani/development/repositories/geotab/image_builder/test.py", line 6, in <module>
    raise RuntimeError("Previous operation failed") from err
RuntimeError: Previous operation failed

请参阅this了解Python2中

from
的支持。

如果异常的链式表示在您的特定应用程序中并不理想,我可以想到两种替代解决方案。

try:
    raise RuntimeError("Original error")
except RuntimeError as e:
    err = e

raise err from None

结果:

$ python test.py
Traceback (most recent call last):
  File "/home/valajeyhani/development/repositories/geotab/image_builder/test.py", line 6, in <module>
    raise err from None
  File "/home/valajeyhani/development/repositories/geotab/image_builder/test.py", line 2, in <module>
    raise RuntimeError("Original error")
RuntimeError: Original error

这不是原始的回溯,但它至少显示了“原始”异常的正确来源,即第 2 行。

最后是第二种不链接异常的方法:

try:
    raise RuntimeError("Original error")
except RuntimeError as e:
    err = e

raise err.with_traceback(err.__traceback__)

输出:

$ python test.py
Traceback (most recent call last):
  File "/home/valajeyhani/development/repositories/geotab/image_builder/test.py", line 6, in <module>
    raise err.with_traceback(err.__traceback__)
  File "/home/valajeyhani/development/repositories/geotab/image_builder/test.py", line 2, in <module>
    raise RuntimeError("Original error")
RuntimeError: Original error

结果与之前的方法非常相似。

© www.soinside.com 2019 - 2024. All rights reserved.