Python:异常装饰器。如何保留堆栈跟踪

Posted

技术标签:

【中文标题】Python:异常装饰器。如何保留堆栈跟踪【英文标题】:Python: Exception decorator. How to preserve stacktrace 【发布时间】:2012-02-18 19:51:47 【问题描述】:

我正在编写一个装饰器来应用于一个函数。它应该捕获任何异常,然后根据原始异常消息引发自定义异常。 (这是因为 suds 抛出了一个通用的 WebFault 异常,我从它的消息中解析 Web 服务抛出的异常并引发 Python 异常来镜像它。)

但是,当我在包装器中引发自定义异常时,我希望堆栈跟踪指向引发原始 WebFault 异常的函数。到目前为止,我提出了正确的异常(它动态解析消息并实例化异常类)。 我的问题:如何保留堆栈跟踪以指向引发 WebFault 异常的原始函数?

from functools import wraps

def try_except(fn):
        def wrapped(*args, **kwargs):
            try:
                fn(*args, **kwargs)
            except Exception, e:
                parser = exceptions.ExceptionParser()
                raised_exception = parser.get_raised_exception_class_name(e)
                exception = getattr(exceptions, raised_exception)
                raise exception(parser.get_message(e))
        return wraps(fn)(wrapped)

【问题讨论】:

您看过traceback 模块吗? docs.python.org/library/traceback.html 包装在装饰器中时使用functools.wrap "Inner exception" (with traceback) in Python? 的可能副本 【参考方案1】:

我在使用自定义装饰器装饰的测试中遇到过这个问题。

我在装饰器主体中使用了以下构造来保留在单元测试输出中打印的原始跟踪:

try:
    result = func(self, *args, **kwargs)
except Exception:
    exc_type, exc_instance, exc_traceback = sys.exc_info()
    formatted_traceback = ''.join(traceback.format_tb(
        exc_traceback))
    message = '\n0\n1:\n2'.format(
        formatted_traceback,
        exc_type.__name__,
        exc_instance.message
    )
    raise exc_type(message)

【讨论】:

是的,但是装饰器的参数是什么? 只是指出与上面代码的区别是traceback.format_tb。谢谢。【参考方案2】:

在 Python 2.x 中,raise 的一个鲜为人知的特性是它可以与多个参数一起使用:raise 的三参数形式接受异常类型、异常实例和追溯。您可以使用sys.exc_info() 获取回溯,它返回(并非巧合)异常类型、异常实例和回溯。

(将异常类型和异常实例视为两个单独的参数的原因是异常类出现之前的产物。)

所以:

import sys

class MyError(Exception):
    pass

def try_except(fn):
    def wrapped(*args, **kwargs):
        try:
            return fn(*args, **kwargs)
        except Exception, e:
            et, ei, tb = sys.exc_info()
            raise MyError, MyError(e), tb
    return wrapped

def bottom():
   1 / 0

@try_except
def middle():
   bottom()

def top():
   middle()

>>> top()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "tmp.py", line 24, in top
    middle()
  File "tmp.py", line 10, in wrapped
    return fn(*args, **kwargs)
  File "tmp.py", line 21, in middle
    bottom()
  File "tmp.py", line 17, in bottom
    1 / 0
__main__.MyError: integer division or modulo by zero

在 Python 3 中,这发生了一些变化。在那里,回溯被附加到异常实例,它们有一个with_traceback 方法:

raise MyError(e).with_traceback(tb)

另一方面,Python 3 也有异常chaining,这在很多情况下更有意义;要使用它,您只需使用:

raise MyError(e) from e

【讨论】:

太好了,谢谢!这完成了这项工作。唯一的问题是我必须将装饰器的声明移动到与被装饰的函数相同的文件中,否则 sys.exc_info() 返回 (None, None, None) - 关于为什么会这样的任何想法? 那……没有意义。 sys.exc_info() 不关心调用者的定义位置。它返回当前正在处理的异常。听起来您在单独文件中的装饰器没有做正确的事情,但是如果没有看到实际代码就很难说。 非常感谢 Py3 中超级方便的语法!超级好用。

以上是关于Python:异常装饰器。如何保留堆栈跟踪的主要内容,如果未能解决你的问题,请参考以下文章

python_如何定义带参数的装饰器?

Python函数之装饰器

Python 装饰器装饰类中的方法

在 Python 中使用装饰器进行 AssertionError 异常处理

python3_装饰器_异常处理

python装饰器实现对异常代码出现进行监控