Unterscheidung zwischen den möglichen Ursprüngen von Ausnahmen, die aus einer zusammengesetzten withAussage hervorgehen
Die Unterscheidung zwischen Ausnahmen, die in einer withAnweisung 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 within ein zu wickeln try .. except. Betrachten Sie das folgende Beispiel (am ValueErrorBeispiel, aber es könnte natürlich durch einen anderen Ausnahmetyp ersetzt werden):
try:
with ContextManager():
BLOCK
except ValueError as err:
print(err)
Hier exceptfä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 withAusfü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 BLOCKund __exit__weil eine Ausnahme, die dem Hauptteil des withTestaments 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 exceptKlausel in den Hauptteil der hinzufügen with, um mögliche Ausnahmen zu speichern, die sonst unbemerkt geblieben wären, und sie mit der exceptspäter im äußersten gefangenen zu vergleichen - wenn sie gleich sind, bedeutet dies, dass der Ursprung war BLOCKoder 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 withAnweisung an. Hier können wir die verschiedenen Teile leicht umwickeln try ... exceptund 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 within einen try ... exceptBlock 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__
...
withAussage bricht eine umgebendetry...exceptAussage nicht auf magische Weise .