Wie kann eine Ausnahme in verschachtelten Try / Except-Blöcken erneut ausgelöst werden?

106

Ich weiß, dass ich, wenn ich eine Ausnahme erneut auslösen möchte, einfach raiseohne Argumente im jeweiligen exceptBlock verwende. Aber gegeben ein verschachtelter Ausdruck wie

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

Wie kann ich das erneut erhöhen, SomeErrorohne die Stapelspur zu unterbrechen? raiseallein würde in diesem Fall die neueren wieder erhöhen AlsoFailsError. Oder wie könnte ich meinen Code umgestalten, um dieses Problem zu vermeiden?

Tobias Kienzler
quelle
2
Haben Sie versucht, eine plan_Bandere Funktion einzufügen, die Trueauf Erfolg und FalseAusnahme zurückgreift? Dann könnte der äußere exceptBlock einfach seinif not try_plan_B(): raise
Drew McGowen
@DrewMcGowen Leider ist die realistischere Fall ist , dass dies in einer Funktion ist beliebige Objekte zu akzeptieren argund ich würde versuchen , Berufung , arg.plan_B()die ein erhöhen könnte AttributeErroraufgrund argkeinen Plan B bereitstellt
Tobias Kienzler
Schauen Sie sich das Traceback-Modul an: docs.python.org/2/library/traceback.html#traceback-examples
Paco
@ Paco Danke, ich werde (obwohl eine Antwort bereits einen einfacheren Weg zeigt)
Tobias Kienzler
@DrewMcGowen Ich habe eine Antwort basierend auf Ihrem Kommentar geschrieben , die weniger pythonisch aussieht als die Antwort von user4815162342 . Aber das liegt daran, dass ich auch einen Rückgabewert haben und plan_BAusnahmen
zulassen

Antworten:

127

Ab Python 3 wird der Traceback in der Ausnahme gespeichert, sodass ein einfacher raise edas (meistens) Richtige tut:

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

Der erzeugte Traceback enthält einen zusätzlichen Hinweis, SomeErrorder während der Bearbeitung aufgetreten ist AlsoFailsError(weil raise eer sich im Inneren befindet except AlsoFailsError). Dies ist irreführend, da das, was tatsächlich passiert ist, umgekehrt ist - wir sind darauf gestoßen AlsoFailsErrorund haben es gehandhabt, während wir versucht haben, uns davon zu erholen SomeError. AlsoFailsErrorErsetzen Sie raise edurch, um einen Traceback zu erhalten, der nicht enthalten ist raise e from None.

In Python 2 speichern Sie den Ausnahmetyp, den Wert und den Traceback in lokalen Variablen und verwenden die Form mit drei Argumentenraise :

try:
    something()
except SomeError:
    t, v, tb = sys.exc_info()
    try:
        plan_B()
    except AlsoFailsError:
        raise t, v, tb
user4815162342
quelle
Perfekt, das habe ich gerade auch hier gefunden , danke! Obwohl dort der Vorschlag ist, raise self.exc_info[1], None, self.exc_info[2]nachdem self.exc_info = sys.exc_info()- [1]aus irgendeinem Grund auf den ersten Platz gebracht
Tobias Kienzler
3
@TobiasKienzler raise t, None, tbverliert den Wert der Ausnahme und erzwingt eine raiseerneute Instanziierung des Typs, wodurch Sie einen weniger spezifischen (oder einfach falschen) Ausnahmewert erhalten. Wenn es sich beispielsweise um eine ausgelöste Ausnahme handelt KeyError("some-key"), wird sie nur erneut ausgelöst KeyError()und der exakt fehlende Schlüssel aus dem Traceback weggelassen.
user4815162342
3
@TobiasKienzler Es sollte immer noch möglich sein, dies in Python 3 als auszudrücken raise v.with_traceback(tb). (Ihr Kommentar sagt sogar so viel, außer es schlägt vor, den Wert erneut zu instanziieren.)
user4815162342
2
Auch die rote Warnung, nicht sys.exc_info()in einer lokalen Variablen zu speichern, war vor Python 2.0 (veröffentlicht vor 13 Jahren) sinnvoll, grenzt aber heute an Lächerlichkeit. Modernes Python wäre ohne den Zykluskollektor nahezu nutzlos, da jede nicht triviale Python-Bibliothek Zyklen ohne Pause erstellt und von ihrer korrekten Bereinigung abhängt.
user4815162342
1
@ user4815162342 Sie können den verschachtelten Fehler "Ein anderer Fehler ist aufgetreten" beenden, indem Sie "Raise e from None" schreiben.
Matthias Urlichs
19

Selbst wenn die akzeptierte Lösung richtig ist, ist es gut, auf die Six- Bibliothek zu verweisen, die eine Python 2 + 3-Lösung verwendet six.reraise.

sechs. reraise ( exc_type , exc_value , exc_traceback = Keine)

Erhöhen Sie eine Ausnahme, möglicherweise mit einem anderen Traceback. [...]

Sie können also schreiben:

import six


try:
    something()
except SomeError:
    t, v, tb = sys.exc_info()
    try:
        plan_B()
    except AlsoFailsError:
        six.reraise(t, v, tb)
Laurent LAPORTE
quelle
1
Ein guter Punkt - six.raise_fromwenn Sie von Sechs sprechen, können Sie ihn auch verwenden, wenn Sie Informationen einschließen möchten, die plan_B()ebenfalls fehlgeschlagen sind.
Tobias Kienzler
1
@TobiasKienzler: Ich denke, es ist eine andere Verwendung: Wenn six.raise_fromSie eine neue Ausnahme erstellen, die mit einer vorherigen verknüpft ist, werden Sie nicht erneut ausgelöst , sodass die Rückverfolgung anders ist.
Laurent LAPORTE
1
Mein Punkt genau - wenn Sie reraiseden Eindruck bekommen nur something()warf SomeError, wenn Sie raise_fromauch wissen , dass dies verursacht plan_B()ausgeführt werden, aber das wirft AlsoFailsError. Es kommt also auf den Anwendungsfall an. Ich denke, raise_fromwird das Debuggen einfacher machen
Tobias Kienzler
9

Wie pro Drew McGowen Vorschlag , sondern von einem allgemeinen Fall (wo ein Rückgabewert die Pflege svorhanden ist), hier ist eine Alternative zu user4815162342 Antwort :

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
quelle
1
Das Schöne an diesem Ansatz ist, dass er in Python 2 und 3 unverändert funktioniert.
user4815162342
2
@ user4815162342 Guter Punkt :) Obwohl ich in Python3 inzwischen darüber nachdenken würde raise from, würde der Stack-Trace mich auch dazu bringen, Plan B fehlgeschlagen zu sehen. Was übrigens in Python 2 emuliert werden kann.
Tobias Kienzler
5

Python 3.5+ hängt die Traceback-Informationen ohnehin an den Fehler an, sodass es nicht mehr erforderlich ist, sie separat zu speichern.

>>> 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
quelle
2
Die Frage bezieht sich auf eine weitere Ausnahme, die während des except. Aber du hast Recht, wenn ich ersetzen err = edurch, sagen sie, raise AttributeErrorerhalten Sie zunächst den SyntaxErrorStack - Trace, gefolgt von einem During handling of the above exception, another exception occurred:und den AttributeErrorStack - Trace. Gut zu wissen, obwohl man sich leider nicht darauf verlassen kann, dass 3.5+ installiert wird. PS: ff verstehen nicht-Deutsche Schwierigkeiten nicht;)
Tobias Kienzler
OK, also habe ich das Beispiel geändert, um eine weitere Ausnahme auszulösen, die (wie die ursprüngliche Frage gestellt) ignoriert wird, wenn ich die erste erneut auslöse.
Matthias Urlichs
3
@TobiasKienzler 3.5+ (in das ich es geändert habe) scheint ein weltweit anerkanntes Format zu sein. War Objekte du? ;)
linusg
@ Linusg Einverstanden :)
Tobias Kienzler