简体   繁体   English

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

[英]Exception traceback is hidden if not re-raised immediately

I've got a piece of code similar to this:我有一段类似的代码:

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()

When func2 raises an exception I receive the following traceback: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

From here I don't see where the exception is coming from.从这里我看不到异常来自哪里。 The original traceback is lost.原始回溯丢失。

How can I preserve original traceback and re-raise it?如何保留原始回溯并重新提出? I want to see something similar to this:我想看到类似的东西:

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

A blank raise raises the last exception.一个空白的raise会引发最后一个异常。

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

If you use raise something Python has no way of knowing if something was an exception just caught before, or a new exception with a new stack trace.如果你使用raise something Python 无法知道something是刚刚捕获的异常,还是带有新堆栈跟踪的新异常。 That's why there is the blank raise that preserves the stack trace.这就是为什么存在保留堆栈跟踪的空白raise

Reference here 参考这里

It is possible to modify and rethrow an exception:可以修改和重新抛出异常:

If no expressions are present, raise re-raises the last exception that was active in the current scope.如果不存在表达式, raise重新引发在当前范围内活动的最后一个异常。 If no exception is active in the current scope, a TypeError exception is raised indicating that this is an error (if running under IDLE, a Queue.Empty exception is raised instead).如果在当前范围内没有活动异常,则会引发TypeError异常,指示这是一个错误(如果在 IDLE 下运行,则会引发Queue.Empty异常)。

Otherwise, raise evaluates the expressions to get three objects, using None as the value of omitted expressions.否则, raise评估表达式以获得三个对象,使用None作为省略表达式的值。 The first two objects are used to determine the type and value of the exception.前两个对象用于确定异常的类型和值。

If a third object is present and not None , it must be a traceback object (see section The standard type hierarchy), and it is substituted instead of the current location as the place where the exception occurred.如果存在第三个对象而不是None ,则它必须是回溯对象(请参阅标准类型层次结构部分),并且将其替换为发生异常的位置而不是当前位置。 If the third object is present and not a traceback object or None , a TypeError exception is raised.如果存在第三个对象而不是回溯对象或None ,则会引发TypeError异常。

The three-expression form of raise is useful to re-raise an exception transparently in an except clause, but raise with no expressions should be preferred if the exception to be re-raised was the most recently active exception in the current scope. raise的三表达式形式对于在except子句中透明地重新引发异常很有用,但如果要重新引发的异常是当前范围内最近活动的异常,则应该首选不带表达式的raise

So if you want to modify the exception and rethrow it, you can do this:所以如果你想修改异常并重新抛出它,你可以这样做:

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

You can get a lot of information about the exception via the sys.exc_info() along with the traceback module您可以通过sys.exc_info()以及traceback模块获取有关异常的大量信息

try the following extension to your code.尝试对您的代码进行以下扩展。

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()

This would print, similar to what you wanted.这将打印,类似于您想要的。

*** 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

While @Jochen's answer works well in the simple case, it is not capable of handling more complex cases, where you are not directly catching and rethrowing, but are for some reason given the exception as an object and wish to re-throw in a completely new context (ie if you need to handle it in a different process).虽然@Jochen 的答案在简单的情况下效果很好,但它无法处理更复杂的情况,在这种情况下您不能直接捕获和重新抛出,而是由于某种原因将异常作为对象并希望完全重新抛出新的上下文(即,如果您需要在不同的过程中处理它)。

In this case, I propose the following:在这种情况下,我提出以下建议:

  1. get the original exc_info获取原始 exc_info
  2. format the original error message, with stack trace使用堆栈跟踪格式化原始错误消息
  3. throw a new exception with that full error message (stack trace incl.) embedded抛出一个嵌入了完整错误消息(包括堆栈跟踪)的新异常

Before you do this, define a new exception type that you will rethrow later...在执行此操作之前,请定义一个稍后将重新抛出的新异常类型...

class ChildTaskException(Exception):
    pass

In the offending code...在有问题的代码中......

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)

Rethrow...重新抛出...

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

Your main function needs to look like this:您的主要功能需要如下所示:

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

This is the standard way of handling (and re-raising) errors.这是处理(和重新引发)错误的标准方法。 Here is a codepad demonstration.这是一个键盘演示。

In Python 3:在 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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM