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

发布于 2024-12-29 02:35:46 字数 762 浏览 3 评论 0原文

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

I am writing a decorator to apply to a function. It should catch any exception, and then raise a custom exception based on the original exception message. (This is because suds throws a generic WebFault exception, from whose message I parse the exception thrown by the web service and raise a Python exception to mirror it.)

However, when I raise the custom exception in the wrapper, I want the stacktrace to point to the function that raised the original WebFault exception. What I have so far raises the correct exception (it dynamically parses the message and instantiates the exception class). My question: How can I preserve the stacktrace to point to the original function that raised the WebFault exception?

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)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

风吹短裙飘 2025-01-05 02:35:46

在 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

In Python 2.x, a little-known feature of raise is that it can be used with more than just one argument: the three-argument form of raise takes the exception type, the exception instance and the traceback. You can get at the traceback with sys.exc_info(), which returns (not coincidentally) the exception type, the exception instance and the traceback.

(The reason this treats the exception type and the exception instance as two separate arguments is an artifact from the days before exception classes.)

So:

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

In Python 3, this changed a little. There, the tracebacks are attached to the exception instance instead, and they have a with_traceback method:

raise MyError(e).with_traceback(tb)

On the other hand Python 3 also has exception chaining, which makes more sense in many cases; to use that, you would just use:

raise MyError(e) from e
赠我空喜 2025-01-05 02:35:46

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

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

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)

I've faced this problem with tests that were decorated with my custom decorators.

I used following construct in decorator body to preserve original trace printed in unittests output:

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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文