2011-01-28 10 views
53

Mam kawałek kodu podobnego do tego:Wyjątek traceback jest ukryty, jeśli nie przebił natychmiast

import sys 

def func1(): 
    func2() 

def func2(): 
    raise Exception('test error') 

def main(): 
    err = None 

    try: 
     func1() 
    except: 
     err = sys.exc_info()[1] 
     pass 

    # some extra processing, involving checking err details (if err is not None) 

    # need to re-raise err so caller can do its own handling 
    if err: 
     raise err 

if __name__ == '__main__': 
    main() 

Kiedy func2 podnosi wyjątek pojawia się następujący Traceback:

Traceback (most recent call last): 
    File "err_test.py", line 25, in <module> 
    main() 
    File "err_test.py", line 22, in main 
    raise err 
Exception: test error 

Z tego miejsca nie widzę, skąd pochodzi wyjątek. Oryginalny traceback zostanie utracony.

Jak mogę zachować oryginalny traceback i ponownie go podnieść? Chcę zobaczyć coś podobnego do tego:

Traceback (most recent call last): 
    File "err_test.py", line 26, in <module> 
    main() 
    File "err_test.py", line 13, in main 
    func1() 
    File "err_test.py", line 4, in func1 
    func2() 
    File "err_test.py", line 7, in func2 
    raise Exception('test error') 
Exception: test error 

Odpowiedz

92

Pusty raise podnosi ostatni wyjątek.

# need to re-raise err so caller can do its own handling 
if err: 
    raise 

Jeśli używasz raise something Python nie ma możliwości dowiedzenia się, czy something Wyjątkiem był tylko złowione wcześniej, czy nowy wyjątek w nowy ślad stosu. Dlatego jest pusty raise, który zachowuje ślad stosu.

Reference here

+2

Warto wspomnieć, że to nie działa w Pythonie 3. – yprez

+8

"To" w komentarzu do yprez oznacza "puste podbicie po opuszczeniu bloku z wyjątkiem". Nagie "podniesienie" działa w Pythonie 3 (ale tylko wewnątrz bloku z wyjątkiem). – jtniehof

4

można uzyskać wiele informacji o wyjątku poprzez sys.exc_info() wraz z modułem traceback

wypróbuj poniższe rozszerzenie kodu.

import sys 
import traceback 

def func1(): 
    func2() 

def func2(): 
    raise Exception('test error') 

def main(): 

    try: 
     func1() 
    except: 
     exc_type, exc_value, exc_traceback = sys.exc_info() 
     # Do your verification using exc_value and exc_traceback 

     print "*** print_exception:" 
     traceback.print_exception(exc_type, exc_value, exc_traceback, 
            limit=3, file=sys.stdout) 

if __name__ == '__main__': 
    main() 

Zostanie wydrukowany w sposób podobny do oczekiwanego.

*** print_exception: 
Traceback (most recent call last): 
    File "err_test.py", line 14, in main 
    func1() 
    File "err_test.py", line 5, in func1 
    func2() 
    File "err_test.py", line 8, in func2 
    raise Exception('test error') 
Exception: test error 
+0

Nie, nie chcę, aby wydrukować go w 'main()'. Chcę ponownie go podnieść z oryginalnym tracebackiem i pozwolić rozmówcy 'main()' na obsłużenie go (np. Zignoruj, wydrukuj na konsoli, zapisz w db, etc). Rozwiązanie Jochen działało. – parxier

1

Twoim głównym zadaniem powinno wyglądać następująco:

def main(): 
    try: 
     func1() 
    except Exception, err: 
     # error processing 
     raise 

Jest to standardowy sposób obsługi (i re-raising) błędów. Here is a codepad demonstration.

+0

Mam wrażenie, że 'z wyjątkiem wyjątków, err:' można ominąć ze starym stylem 'podnieść 'zły wyjątek" 'sposób wychodzenia z wyjątków – parxier

+0

@parxier następnie użyj' z wyjątkiem obiektu, err' –

+0

To nie różni się od 'err = sys.exc_info() [1] '. W każdym razie głównym celem było ponowne podniesienie 'err' poza blokiem' except' bez utraty oryginalnego tracebacku. Rozwiązanie Jochen działało. – parxier

53

Możliwe jest modify and rethrow wyjątek:

przypadku braku wyrażenia są obecne, raise ponownie podnosi ostatniego wyjątku że był aktywny w bieżącym zakresie. Jeśli żaden wyjątek nie jest aktywny w zakresie bieżącym , zgłaszany jest wyjątek TypeError wskazujący, że jest to błąd (jeśli działa pod IDLE, zamiast tego zostaje zgłoszony wyjątek Queue.Empty ).

W przeciwnym razie, raise oblicza wyrażenia, aby uzyskać trzy obiekty, używając jako wartości pominiętych wyrażeń wartości None. Dwa pierwsze obiekty to używane do określenia typu i wartości wyjątku.

Jeżeli Trzecim celem jest obecne i nie None musi być traceback przedmiot (patrz punkt Standardowe hierarchii typ) i jest podstawiony zamiast bieżącej lokalizacji jako miejsce, w którym nastąpiło wyłączenie .Jeśli trzeci obiekt jest obecny, a nie obiekt śledzący lub None, zgłoszony zostanie wyjątek.

Trzy ekspresja forma raise jest użyteczny do ponownego podniesienia wyjątek przejrzysty w except klauzuli ale raise bez ekspresji powinno być korzystne, gdy wyjątek ponownie podniósł się ostatnio aktywny wyjątek w bieżącym zakresie.

Więc jeśli chcesz zmodyfikować wyjątek i przekaż go, można to zrobić:

try: 
    buggy_code_which_throws_exception() 
except Exception as e: 
    raise Exception, "The code is buggy: %s" % e, sys.exc_info()[2] 
+0

Interesujące. Przyjęta odpowiedź lepiej radzi sobie z przypadkiem użycia PO, ale jest to interesujące jako ogólniejsza odpowiedź. Nie widzę w tym jednak zbyt wiele, ponieważ traceback staje się naprawdę mylący, jeśli złapiesz, powiedzmy, "ValueError" i podniosę "RuntimeError" (nie widzisz, że kiedykolwiek był zaangażowany ValueError), i jedyne przypadki, które spotkałem osobiście, gdy chciałem zachować traceback, ale robię coś bardziej złożonego niż tylko "raise" bez argumentów, to były przypadki, w których chciałem podnieść wyjątek innego rodzaju. –

+4

Użyłem tego do ponownego wysłania tego samego wyjątku z inną wiadomością, w tym więcej szczegółów na temat warunków, które spowodowały wyjątek, które są dostępne w zewnętrznym zakresie, ale nie w środku. – qris

+1

Czasami istnieją bardzo dobre powody do stosowania tej formy wyrażania haseł.Twoja odpowiedź właśnie pomogła mi napisać dekorator, który owija testy integracyjne, aw przypadku niepowodzenia robi zrzut ekranu. A następnie podnosi niepowodzenie oryginalnego twierdzenia. Problem polegał na tym, że traceback został zepchnięty przez try/excepts na ekranie z kodem. Więc dziękuję! – aychedee

1

Podczas @ odpowiedź Jochen za dobrze w prostym przypadku, nie jest w stanie obsługiwać bardziej złożone przypadki , gdzie nie łapiesz bezpośrednio i rethrowing, ale są z jakiegoś powodu dany wyjątek jako obiekt i chcesz ponownie rzucić w zupełnie nowy kontekst (tj. jeśli potrzebujesz obsługiwać go w innym procesie).

W tym przypadku proponuję następujące:

  1. uzyskać oryginalną exc_info
  2. formatu oryginalny komunikat o błędzie, z ślad stosu
  3. rzucać nowy wyjątek z tej wiadomości pełne błędów (ślad stosu zaw.) osadzony

Zanim to zrobisz, należy zdefiniować nowy typ wyjątku, że będzie rethrow później ...

class ChildTaskException(Exception): 
    pass 

W kodzie naruszającego ...

import sys 
import traceback 

try: 
    # do something dangerous 
except: 
    error_type, error, tb = sys.exc_info() 
    error_lines = traceback.format_exception(error_type, error, tb) 
    error_msg = ''.join(error_lines) 
    # for example, if you are doing multiprocessing, you might want to send this to another process via a pipe 
    connection.send(error_msg) 

rethrow ...

# again, a multiprocessing example of receiving that message through a pipe 
error_msg = pcon.recv() 
raise ChildTaskException(error_msg) 
Powiązane problemy