2012-01-25 10 views
19

Piszę dekorator, aby zastosować do funkcji. Powinien przechwycić każdy wyjątek, a następnie podnieść niestandardowy wyjątek na podstawie oryginalnego komunikatu wyjątku. (Dzieje się tak dlatego, że suds generuje ogólny wyjątek WebFault, z którego wiadomości analizuję wyjątek generowany przez usługę WWW i zgłaszam wyjątek Pythona, aby odzwierciedlić go.)Python: dekorator wyjątków. Jak zachować stacktrace

Jednak po podniesieniu niestandardowego wyjątku w opakowaniu, chcesz, aby stacktrace wskazywało na funkcję, która podniosła oryginalny wyjątek WebFault. Do tej pory podniosłem poprawny wyjątek (dynamicznie analizuje komunikat i tworzy instancję klasy wyjątku). Moje pytanie: Jak mogę zachować stacktrace, aby wskazać oryginalną funkcję, która spowodował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) 
+1

Czy spojrzał na 'traceback' moduł? http://docs.python.org/library/traceback.html – stderr

+0

Podczas zawijania w dekoratorze użyj [functools.wrap] (https://docs.python.org/2/library/functools.html) –

+0

możliwy duplikat ["Wewnętrzny wyjątek" (z tracebackiem) w Pythonie?] (Http://stackoverflow.com/questions/1350671/inner-exception-with-traceback-in-python) –

Odpowiedz

37

Pythona 2.x, mało znane raise cechą jest to, że może on być stosowany z więcej niż jednego argumentu postać trzech argument raise wykonuje typ wyjątku wystąpienie wyjątek i traceback. Możesz uzyskać w systemie zwrotnym z sys.exc_info(), który zwraca (nieprzypadkowo) typ wyjątku, wyjątek i traceback.

(Powodem tego traktuje typ wyjątku i wystąpienie wyjątku jako dwa oddzielne argumentów jest artefakt z czasów przed klas 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ę zmieniło trochę. Tam tracebacks są dołączone do instancji wyjątku zamiast, i mają with_traceback metody:

raise MyError(e).with_traceback(tb) 

Z drugiej strony Python 3 ma również wyjątek łańcuchowym, który ma większy sens w wielu przypadkach; Aby go użyć, wystarczy użyć:

raise MyError(e) from e 
+0

Świetnie, dzięki! To się stało. Jedynym problemem było to, że musiałem przenieść deklarację dekoratora do tego samego pliku, co funkcja, która została udekorowana, w przeciwnym razie sys.exc_info() zwróciła (Brak, Brak, Brak) - wszelkie pomysły, dlaczego tak się stało? – igniteflow

+0

To ... nie ma sensu. sys.exc_info() nie dba o to, gdzie zdefiniowany jest wywołujący. Zwraca aktualnie obsługiwany wyjątek. Wygląda na to, że dekorator, który miałeś w oddzielnym pliku, nie postąpił właściwie, ale trudno powiedzieć, nie widząc prawdziwego kodu. –

4

Zetknąłem się z tym problemem z testami, które zostały ozdobione moimi niestandardowymi dekoratorami.

użyłem następujących skonstruować w organizmie dekorator aby zachować oryginalny ślad wydrukowany na 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) 
+0

tak, ale jakie są argumenty dla dekoratora? –

+0

Wystarczy wskazać, że różnica w stosunku do powyższego kodu to 'traceback.format_tb'. Dzięki. – Ehvince