Jak ponownie zgłosić wyjątek w zagnieżdżonych blokach try / except?

109

Wiem, że jeśli chcę ponownie zgłosić wyjątek, po prostu używam raisebez argumentów w odpowiednim exceptbloku. Ale biorąc pod uwagę zagnieżdżone wyrażenie, takie jak

try:
    something()
except SomeError as e:
    try:
        plan_B()
    except AlsoFailsError:
        raise e  # I'd like to raise the SomeError as if plan_B()
                 # didn't raise the AlsoFailsError

jak mogę ponownie podbić SomeErrorbez przerywania śledzenia stosu? raisesam w tym przypadku ponownie podniósłby nowsze AlsoFailsError. Albo jak mogę zmienić kod, aby uniknąć tego problemu?

Tobias Kienzler
źródło
2
Czy próbowałeś wstawić plan_Binną funkcję, która zwraca Truepo sukcesie i Falsepo wyjątku? Wtedy zewnętrzny exceptblok mógłby być po prostuif not try_plan_B(): raise
Drew McGowen
@DrewMcGowen Niestety bardziej realistycznym przypadkiem jest to, że jest to wewnątrz funkcji akceptującej dowolne obiekty argi spróbuję zadzwonić, arg.plan_B()co może AttributeErrorspowodować podniesienie z powodu argbraku planu B
Tobias Kienzler
Zapoznaj się z modułem traceback
Paco
@Paco Dzięki, zrobię to (chociaż odpowiedź już pokazuje prostszy sposób)
Tobias Kienzler
@DrewMcGowen Napisałem odpowiedź na podstawie twojego komentarza , który wygląda mniej pythonowo niż odpowiedź użytkownika4815162342 . Ale to dlatego, że chcę mieć również wartość zwracaną i pozwalam plan_Bna zgłaszanie wyjątków
Tobias Kienzler.

Odpowiedzi:

131

Począwszy od Pythona 3, śledzenie jest przechowywane w wyjątku, więc prostota raise ezrobi (głównie) właściwą rzecz:

try:
    something()
except SomeError as e:
    try:
        plan_B()
    except AlsoFailsError:
        raise e  # or raise e from None - see below

Utworzone dane śledzenia będą zawierały dodatkowe powiadomienie, które SomeErrorwystąpiło podczas obsługi AlsoFailsError(ponieważ raise eznajduje się w środku except AlsoFailsError). Jest to mylące, ponieważ to, co faktycznie się stało, jest odwrotne - napotkaliśmy AlsoFailsErrori poradziliśmy sobie z tym, próbując odzyskać siły SomeError. Aby uzyskać traceback, który nie zawiera AlsoFailsErrorwymienić raise ez raise e from None.

W Pythonie 2 zapisujesz typ wyjątku, wartość i dane śledzenia w zmiennych lokalnych i używasz trzyargumentowej formyraise :

try:
    something()
except SomeError:
    t, v, tb = sys.exc_info()
    try:
        plan_B()
    except AlsoFailsError:
        raise t, v, tb
user4815162342
źródło
Doskonale, właśnie to znalazłem tutaj , dzięki! Chociaż sugestia jest raise self.exc_info[1], None, self.exc_info[2]po self.exc_info = sys.exc_info()- postawieniu [1]na pierwszym miejscu z jakiegoś powodu
Tobias Kienzler
3
@TobiasKienzler raise t, None, tbutraci wartość wyjątku i wymusi raiseponowne utworzenie jego wystąpienia z typu, podając mniej konkretną (lub po prostu niepoprawną) wartość wyjątku. Na przykład, jeśli podniesiony wyjątek to KeyError("some-key"), po prostu podniesie ponownie KeyError()i pominie dokładny brakujący klucz z śledzenia.
user4815162342
3
@TobiasKienzler W Pythonie 3 nadal powinno być możliwe wyrażenie tego jako raise v.with_traceback(tb). (Twój komentarz mówi nawet to samo, z wyjątkiem tego, że proponuje ponowne utworzenie instancji wartości.)
user4815162342
2
Ponadto czerwone ostrzeżenie, aby nie przechowywać sys.exc_info()w zmiennej lokalnej, miało sens przed wersją Python 2.0 (wydaną 13 lat temu), ale dziś graniczy z absurdem. Nowoczesny Python byłby prawie bezużyteczny bez kolektora cykli, ponieważ każda nietrywialna biblioteka Pythona tworzy cykle bez przerwy i zależy od ich prawidłowego czyszczenia.
user4815162342
1
@ user4815162342 Możesz zabić zagnieżdżony błąd „wystąpił inny błąd”, pisząc „podnieś e z żadnego”.
Matthias Urlichs
19

Nawet jeśli przyjęte rozwiązanie jest słuszne, dobrze jest wskazać bibliotekę Six, która ma rozwiązanie Python 2 + 3, używając six.reraise.

sześć. przebicie ( exc_type , exc_value , exc_traceback = nie)

Ponownie wprowadź wyjątek, prawdopodobnie z innym identyfikatorem. […]

Możesz więc napisać:

import six


try:
    something()
except SomeError:
    t, v, tb = sys.exc_info()
    try:
        plan_B()
    except AlsoFailsError:
        six.reraise(t, v, tb)
Laurent LAPORTE
źródło
1
Dobra uwaga - mówiąc o Six, możesz również użyć, six.raise_fromjeśli chcesz dołączyć informacje, które plan_B()również zawiodły.
Tobias Kienzler
1
@TobiasKienzler: Myślę, że to inne użycie: six.raise_fromtworząc nowy wyjątek, który jest powiązany z poprzednim, nie przebijasz ponownie , więc śledzenie wstecz jest inne.
Laurent LAPORTE
1
Chodzi mi dokładnie o to - jeśli masz reraisewrażenie tylko something()rzucone SomeError, jeśli raise_fromwiesz również, że spowodowało plan_B()to wykonanie, ale rzucanie AlsoFailsError. Więc to zależy od zastosowania. Myślę, że raise_fromułatwi to debugowanie
Tobias Kienzler
9

Zgodnie z sugestią Drew McGowena , ale biorąc pod uwagę ogólny przypadek (gdzie sobecna jest wartość zwracana ), oto alternatywa dla odpowiedzi użytkownika4815162342 :

try:
    s = something()
except SomeError as e:
    def wrapped_plan_B():
        try:
            return False, plan_B()
        except:
            return True, None
    failed, s = wrapped_plan_B()
    if failed:
        raise
Tobias Kienzler
źródło
1
Zaletą
2
@ user4815162342 Dobra uwaga :) Chociaż w międzyczasie rozważałbym w Pythonie3 raise from, więc śledzenie stosu pozwoliłoby mi również na niepowodzenie planu B. Które można przy okazji emulować w Pythonie 2 .
Tobias Kienzler
5

Python 3.5+ i tak dołącza informacje śledzenia do błędu, więc nie jest już konieczne zapisywanie ich osobno.

>>> def f():
...   try:
...     raise SyntaxError
...   except Exception as e:
...     err = e
...     try:
...       raise AttributeError
...     except Exception as e1:
...       raise err from None
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in f
  File "<stdin>", line 3, in f
SyntaxError: None
>>> 
Matthias Urlichs
źródło
2
Pytanie dotyczy kolejnego wyjątku, który miał miejsce podczas except. Ale masz rację, kiedy zastępuję err = e, powiedzmy, raise AttributeErrorpierwszySyntaxError ślad stosu, a następnie a During handling of the above exception, another exception occurred:i AttributeErrorślad stosu. Dobrze wiedzieć, ale niestety nie można polegać na zainstalowaniu 3.5+. PS: ff verstehen nicht-Deutsche vermutlich nicht;)
Tobias Kienzler
OK, więc zmieniłem przykład, aby zgłosić inny wyjątek, który (zgodnie z pierwotnym pytaniem) jest ignorowany, gdy ponownie podnoszę pierwszy.
Matthias Urlichs
3
@TobiasKienzler 3.5+ (na który go zmieniłem) wydaje się być formatem rozpoznawanym na całym świecie. Czy denkst du? ;)
linusg
@linusg Agreed :)
Tobias Kienzler,