我正在编写一个装饰器来应用于一个函数。它应该捕获任何异常,然后根据原始异常消息引发自定义异常。 (这是因为suds抛出一个通用的WebFault异常,我从它的消息中解析Web服务引发的异常并引发一个Python异常来镜像它。)
但是,当我在包装器中引发自定义异常时,我希望stacktrace指向引发原始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)
答案 0 :(得分:41)
在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
答案 1 :(得分:5)
我在使用自定义装饰器修饰的测试中遇到了这个问题。
我在装饰器主体中使用了以下构造来保留在unittests输出中打印的原始轨迹:
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)