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

Posted

技术标签:

【中文标题】如果不立即重新引发异常回溯,则隐藏【英文标题】:Exception traceback is hidden if not re-raised immediately 【发布时间】:2011-06-17 01:11:41 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

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

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

    获取原始 exc_info 使用堆栈跟踪格式化原始错误消息 抛出一个嵌入了完整错误消息(包括堆栈跟踪)的新异常

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

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】:

空白raise 引发最后一个异常。

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

如果您使用raise something,Python 无法知道something 是刚刚捕获的异常,还是带有新堆栈跟踪的新异常。这就是保留堆栈跟踪的空白raise 的原因。

Reference here

【讨论】:

值得一提的是,这在 Python 3 中不起作用。 "This" 在 yprez 的评论中的意思是“离开 except 块后的空加注”。裸露的“raise”在 Python 3 中确实有效(但仅在 except 块内部。)【参考方案3】:

有可能modify and rethrow异常:

如果没有表达式,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]

【讨论】:

有趣。接受的答案比这更好地处理 OP 的用例,但这作为更一般的答案很有趣。我看不出它有多大用处,因为如果您捕获 ValueError 并提出 RuntimeError (那么您看不到 ValueError 曾经涉及),回溯会变得非常误导,并且当我想保留回溯但做一些比raise 更复杂的事情时,我个人遇到的唯一情况是我想提出不同类型的异常。 我已经用它来重新抛出相同的异常,但带有不同的消息,包括有关导致异常的条件的更多详细信息,这些条件在外部范围内可用,但在内部范围内不可用。 有时使用 raise 的这三种表达形式有很好的理由。您的回答只是帮助我编写了一个包装集成测试的装饰器,并在失败时截取屏幕截图。然后引发原始断言失败。问题是回溯被截屏代码中的 try/excepts 破坏了。所以谢谢! 有没有办法以 Python 2 和 Python 3 兼容的方式做到这一点?我在 Python 3 中遇到 SyntaxError。 @elias six.reraise(exc_type, exc_value, exc_traceback=None)【参考方案4】:

您可以通过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

【讨论】:

不,我不想在main() 中打印它。我想用原始回溯重新引发它,并让main() 的调用者来处理它(例如忽略、打印到控制台、保存到数据库等)。 Jochen 的解决方案奏效了。 这将是 Python3 的最佳答案,如果您将 print 更改为 raise exc_type.with_traceback(exc_value, exc_traceback) 之类的东西【参考方案5】:

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

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

这是处理(和重新引发)错误的标准方法。 Here is a codepad demonstration.

【讨论】:

我感觉except Exception, err:可以用老式的raise "bad exception"引发异常的方式绕过 @parxier 然后使用except object, err err = sys.exc_info()[1]没有什么不同。无论如何,重点是在except 块之外重新提出err,而不会丢失原始回溯。 Jochen 的解决方案奏效了。

以上是关于如果不立即重新引发异常回溯,则隐藏的主要内容,如果未能解决你的问题,请参考以下文章

子进程子回溯

回溯 八皇后

如何在没有回溯的情况下退出 Python?

Python Execnet 和异常处理

有人可以帮助解释这个回溯算法中的递归吗?

五大经典算法之回溯法