Python: dekorator WYJĄTKÓW. Jak zachować stacktrace
Piszę dekorator do zastosowania do funkcji. Powinien wychwycić dowolny wyjątek, a następnie wywołać Niestandardowy wyjątek na podstawie oryginalnego Komunikatu o wyjątku. (Dzieje się tak dlatego, że suds rzuca ogólny wyjątek WebFault, z którego wiadomości analizuję wyjątek rzucony przez usługę internetową i podnoszę wyjątek Pythona, aby go odbić.)
Jednakże, gdy podniosę Niestandardowy wyjątek w opakowaniu, chcę, aby stacktrace wskazywało na funkcję, która podniosła oryginał Wyjątek WebFault. To co mam do tej pory powoduje powstanie WŁAŚCIWEGO wyjątku (dynamicznie przetwarza wiadomość i tworzy instancję klasy wyjątku). moje pytanie: Jak mogę zachować stacktrace, aby wskazać oryginalną funkcję, która wywołała wyjątek 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 answers
W Pythonie 2.x, mało znaną cechą raise
jest to, że może być używany z więcej niż jednym argumentem: trójargumentowa forma raise
przyjmuje typ wyjątku, instancję wyjątku i traceback. Traceback można uzyskać za pomocą sys.exc_info()
, która zwraca (nieprzypadkowo) typ wyjątku, instancję wyjątku i traceback.
(powodem, dla którego traktuje typ wyjątku i instancję wyjątku jako dwa oddzielne argumenty, jest artefakt z dni przed klasy WYJĄTKÓW.)
Więc:
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
W Pythonie 3, to się trochę zmieniło. Tam, tracebacki są dołączone do instancji wyjątku i mają metodę with_traceback
:
raise MyError(e).with_traceback(tb)
Z drugiej strony Python 3 ma również wyjątek chaining, co w wielu przypadkach ma większy sens; aby go użyć, wystarczy użyć:
raise MyError(e) from e
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-25 16:40:50
Miałem do czynienia z tym problemem z testami, które zostały udekorowane moimi niestandardowymi dekoratorami.
Użyłem następującej konstrukcji w ciele dekoratora, aby zachować oryginalny ślad wydrukowany w wyjściu 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)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-11 21:45:49