Unterscheidung zwischen den möglichen Ursprüngen von Ausnahmen, die aus einer zusammengesetzten with
Aussage hervorgehen
Die Unterscheidung zwischen Ausnahmen, die in einer with
Anweisung auftreten, ist schwierig, da sie an verschiedenen Stellen auftreten können. Ausnahmen können von einer der folgenden Stellen (oder darin genannten Funktionen) ausgelöst werden:
ContextManager.__init__
ContextManager.__enter__
- der Körper der
with
ContextManager.__exit__
Weitere Informationen finden Sie in der Dokumentation zu Context Manager-Typen .
Wenn wir zwischen diesen verschiedenen Fällen unterscheiden wollen, reicht es nicht aus , nur das with
in ein zu wickeln try .. except
. Betrachten Sie das folgende Beispiel (am ValueError
Beispiel, aber es könnte natürlich durch einen anderen Ausnahmetyp ersetzt werden):
try:
with ContextManager():
BLOCK
except ValueError as err:
print(err)
Hier except
fängt der Wille Ausnahmen ab, die von allen vier verschiedenen Orten ausgehen, und erlaubt daher keine Unterscheidung zwischen ihnen. Wenn wir die Instanziierung des Kontextmanagerobjekts außerhalb von verschieben with
, können wir unterscheiden zwischen __init__
und BLOCK / __enter__ / __exit__
:
try:
mgr = ContextManager()
except ValueError as err:
print('__init__ raised:', err)
else:
try:
with mgr:
try:
BLOCK
except TypeError: # catching another type (which we want to handle here)
pass
except ValueError as err:
# At this point we still cannot distinguish between exceptions raised from
# __enter__, BLOCK, __exit__ (also BLOCK since we didn't catch ValueError in the body)
pass
Tatsächlich hat dies nur beim __init__
Teil geholfen, aber wir können eine zusätzliche Sentinel-Variable hinzufügen, um zu überprüfen, ob der Hauptteil der with
Ausführung ausgeführt wurde (dh zwischen __enter__
und den anderen zu unterscheiden):
try:
mgr = ContextManager() # __init__ could raise
except ValueError as err:
print('__init__ raised:', err)
else:
try:
entered_body = False
with mgr:
entered_body = True # __enter__ did not raise at this point
try:
BLOCK
except TypeError: # catching another type (which we want to handle here)
pass
except ValueError as err:
if not entered_body:
print('__enter__ raised:', err)
else:
# At this point we know the exception came either from BLOCK or from __exit__
pass
Der schwierige Teil besteht darin, zwischen Ausnahmen zu unterscheiden, die von BLOCK
und __exit__
weil eine Ausnahme, die dem Hauptteil des with
Testaments entgeht, übergeben wird, an __exit__
die entschieden werden kann, wie damit umgegangen werden soll (siehe die Dokumente ). Wenn sich jedoch etwas __exit__
auslöst, wird die ursprüngliche Ausnahme durch die neue ersetzt. Um diese Fälle zu behandeln, können wir eine allgemeine except
Klausel in den Hauptteil der hinzufügen with
, um mögliche Ausnahmen zu speichern, die sonst unbemerkt geblieben wären, und sie mit der except
später im äußersten gefangenen zu vergleichen - wenn sie gleich sind, bedeutet dies, dass der Ursprung war BLOCK
oder anders war es __exit__
(falls __exit__
die Ausnahme unterdrückt wird, indem ein wahrer Wert als äußerster zurückgegeben wirdexcept
wird einfach nicht ausgeführt).
try:
mgr = ContextManager() # __init__ could raise
except ValueError as err:
print('__init__ raised:', err)
else:
entered_body = exc_escaped_from_body = False
try:
with mgr:
entered_body = True # __enter__ did not raise at this point
try:
BLOCK
except TypeError: # catching another type (which we want to handle here)
pass
except Exception as err: # this exception would normally escape without notice
# we store this exception to check in the outer `except` clause
# whether it is the same (otherwise it comes from __exit__)
exc_escaped_from_body = err
raise # re-raise since we didn't intend to handle it, just needed to store it
except ValueError as err:
if not entered_body:
print('__enter__ raised:', err)
elif err is exc_escaped_from_body:
print('BLOCK raised:', err)
else:
print('__exit__ raised:', err)
Alternativer Ansatz unter Verwendung der in PEP 343 genannten äquivalenten Form
PEP 343 - Die "with" -Anweisung gibt eine äquivalente "non-with" -Version der with
Anweisung an. Hier können wir die verschiedenen Teile leicht umwickeln try ... except
und so zwischen den verschiedenen möglichen Fehlerquellen unterscheiden:
import sys
try:
mgr = ContextManager()
except ValueError as err:
print('__init__ raised:', err)
else:
try:
value = type(mgr).__enter__(mgr)
except ValueError as err:
print('__enter__ raised:', err)
else:
exit = type(mgr).__exit__
exc = True
try:
try:
BLOCK
except TypeError:
pass
except:
exc = False
try:
exit_val = exit(mgr, *sys.exc_info())
except ValueError as err:
print('__exit__ raised:', err)
else:
if not exit_val:
raise
except ValueError as err:
print('BLOCK raised:', err)
finally:
if exc:
try:
exit(mgr, None, None, None)
except ValueError as err:
print('__exit__ raised:', err)
Normalerweise reicht ein einfacherer Ansatz aus
Die Notwendigkeit einer solchen speziellen Ausnahmebehandlung sollte ziemlich selten sein, und normalerweise ist es ausreichend, das Ganze with
in einen try ... except
Block zu wickeln . Insbesondere wenn die verschiedenen Fehlerquellen durch unterschiedliche (benutzerdefinierte) Ausnahmetypen angezeigt werden (die Kontextmanager müssen entsprechend gestaltet sein), können wir leicht zwischen ihnen unterscheiden. Zum Beispiel:
try:
with ContextManager():
BLOCK
except InitError: # raised from __init__
...
except AcquireResourceError: # raised from __enter__
...
except ValueError: # raised from BLOCK
...
except ReleaseResourceError: # raised from __exit__
...
with
Aussage bricht eine umgebendetry...except
Aussage nicht auf magische Weise .