Python: 异常装饰器怎么保留堆栈追踪
我正在写一个装饰器,用来给一个函数加上功能。这个装饰器的作用是捕捉任何异常,然后根据原始异常的信息抛出一个自定义的异常。(这是因为suds会抛出一个通用的WebFault异常,我需要从这个异常的信息中解析出是哪个网络服务抛出的异常,然后抛出一个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 个回答
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)
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中,这个情况稍微有些变化。在Python 3里,追踪信息是和异常实例绑定在一起的,并且它有一个with_traceback
的方法:
raise MyError(e).with_traceback(tb)
另一方面,Python 3还引入了异常链式调用的概念,这在很多情况下更有意义;要使用这个功能,你只需要:
raise MyError(e) from e