Rozróżnienie między możliwymi źródłami wyjątków zgłoszonych w withinstrukcji złożonej
Rozróżnianie wyjątków występujących w withinstrukcji 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 withw a try .. exceptnie jest wystarczające. Rozważ następujący przykład (wykorzystując ValueErrorjako 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 exceptwychwycą 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 withrozpoczę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 BLOCKi __exit__ponieważ wyjątek, który ucieka z treści, withzostanie 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 excepttekście ogólną klauzulę, withaby 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 exceptpóźniej w skrajnym otoczeniu - jeśli są one takie same, oznacza to, że BLOCKlub 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ę withinstrukcji „nie z” . Tutaj możemy łatwo owinąć różne części, try ... excepta 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 withw try ... exceptbloku 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__
...
withOświadczenie nie magicznie złamać otaczającątry...exceptoświadczenie.