Rozróżnienie między możliwymi źródłami wyjątków zgłoszonych w with
instrukcji złożonej
Rozróżnianie wyjątków występujących w with
instrukcji jest trudne, ponieważ mogą pochodzić z różnych miejsc. Wyjątki można zgłaszać z jednego z następujących miejsc (lub wywoływanych tam funkcji):
ContextManager.__init__
ContextManager.__enter__
- ciało
with
ContextManager.__exit__
Aby uzyskać więcej informacji, zobacz dokumentację dotyczącą typów menedżera kontekstu .
Jeśli chcemy rozróżnić te różne przypadki, samo zawinięcie with
w a try .. except
nie jest wystarczające. Rozważ następujący przykład (wykorzystując ValueError
jako przykład, ale oczywiście można go zastąpić dowolnym innym typem wyjątku):
try:
with ContextManager():
BLOCK
except ValueError as err:
print(err)
Tutaj except
wychwycą wyjątki pochodzące ze wszystkich czterech różnych miejsc, a zatem nie pozwalają na ich rozróżnienie. Jeśli przeniesiemy instancję obiektu menedżera kontekstu poza with
, możemy rozróżnić __init__
i 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
W rzeczywistości pomogło to w __init__
części, ale możemy dodać dodatkową zmienną wartowniczą, aby sprawdzić, czy ciało with
rozpoczęte do wykonania (tj. Rozróżnienie między __enter__
innymi):
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
Trudna część polega na rozróżnieniu wyjątków pochodzących od BLOCK
i __exit__
ponieważ wyjątek, który ucieka z treści, with
zostanie przekazany, __exit__
który może zdecydować, jak sobie z tym poradzić (zobacz dokumenty ). Jeśli jednak się __exit__
podniesie, oryginalny wyjątek zostanie zastąpiony nowym. Aby poradzić sobie z tymi przypadkami, możemy dodać w except
tekście ogólną klauzulę, with
aby zapisać każdy potencjalny wyjątek, który w przeciwnym razie uniknąłby niezauważenia, i porównać go z tym, który został schwytany except
później w skrajnym otoczeniu - jeśli są one takie same, oznacza to, że BLOCK
lub w inny sposób był __exit__
(w przypadku, gdy __exit__
tłumi wyjątek, zwracając prawdziwą wartość na zewnątrz)except
po prostu nie zostanie wykonany).
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)
Alternatywne podejście z wykorzystaniem równoważnej formy wymienionej w PEP 343
PEP 343 - Instrukcja „z” określa równoważną wersję with
instrukcji „nie z” . Tutaj możemy łatwo owinąć różne części, try ... except
a tym samym rozróżnić różne potencjalne źródła błędów:
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)
Zwykle prostsze podejście wystarczy
Konieczność takiej specjalnej obsługi wyjątków powinny być bardzo rzadko i zwykle owinięcie całości with
w try ... except
bloku będzie wystarczająca. Zwłaszcza jeśli różne źródła błędów są wskazywane przez różne (niestandardowe) typy wyjątków (menedżery kontekstów muszą być odpowiednio zaprojektowane), możemy łatwo je rozróżnić. Na przykład:
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
Oświadczenie nie magicznie złamać otaczającątry...except
oświadczenie.