Python: Exception decorator. How to preserve stacktrace

后端 未结 2 2012
伪装坚强ぢ
伪装坚强ぢ 2020-12-24 08:15

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

相关标签:
2条回答
  • 2020-12-24 08:25

    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)
    
    0 讨论(0)
  • 2020-12-24 08:33

    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
    
    0 讨论(0)
提交回复
热议问题