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

20
我正在编写一个装饰器来应用于一个函数。它应该捕获任何异常,然后根据原始异常消息引发自定义异常。(这是因为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)

2
你看过traceback模块吗?http://docs.python.org/library/traceback.html - stderr
在使用装饰器包装时,请使用 functools.wrap - Mr_and_Mrs_D
可能是["Inner exception" (with traceback) in Python?]的重复问题。(https://dev59.com/6HM_5IYBdhLWcg3wgjW2) - Mr_and_Mrs_D
2个回答

43
在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还具有异常链接的功能,在许多情况下更为合理;要使用它,只需使用:

raise MyError(e) from e

太好了,谢谢!这解决了问题。唯一的问题是,我不得不将装饰器的声明移动到与被装饰的函数相同的文件中,否则sys.exc_info()会返回(None,None,None)- 你有什么想法为什么会这样? - igniteflow
那...没有意义。sys.exc_info() 这个函数不在乎调用者在哪里定义。它返回当前正在处理的异常。听起来你在另一个文件中使用的装饰器并没有做正确的事情,但是如果没有看到实际的代码就很难确定。 - Thomas Wouters
Py3的超级方便的语法被很好地提到了!非常有用。 - logicOnAbstractions

5

我曾经遇到过使用我的自定义装饰器进行测试时出现的问题。

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

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 = '\n{0}\n{1}:\n{2}'.format(
        formatted_traceback,
        exc_type.__name__,
        exc_instance.message
    )
    raise exc_type(message)

是的,但是装饰器的参数是什么? - Terrence Brannon
只是想指出与上面代码的不同之处在于 traceback.format_tb。谢谢。 - Ehvince

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接