Подтвердить что ты не робот

Python: декоратор оформления. Как сохранить stacktrace

Я пишу декоратор для применения к функции. Он должен поймать любое исключение, а затем создать настраиваемое исключение на основе исходного сообщения об исключении. (Это объясняется тем, что suds генерирует общее исключение WebFault, из сообщения которого я анализирую исключение, созданное веб-службой, и создаю исключение Python для его зеркалирования.)

Однако, когда я создаю настраиваемое исключение в оболочке, я хочу, чтобы stacktrace указывал на функцию, которая вызывала исходное исключение WebFault. То, что я до сих пор вызывает правильное исключение (оно динамически анализирует сообщение и создает экземпляр класса исключения). Мой вопрос: Как я могу сохранить stacktrace, чтобы указать на исходную функцию, которая вызвала исключение 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)
4b9b3361

Ответ 1

В 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

Ответ 2

Я столкнулся с этой проблемой с тестами, которые были украшены моими декоративными декораторами.

Я использовал следующую конструкцию в корпусе декоратора, чтобы сохранить исходную трассировку, напечатанную на выходе 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)