複合with
ステートメントから発生する例外の考えられる原因を区別する
with
ステートメントで発生する例外は異なる場所で発生する可能性があるため、区別するのは難しいです。例外は、次の場所(またはそこで呼び出される関数)のいずれかから発生する可能性があります。
ContextManager.__init__
ContextManager.__enter__
- の体
with
ContextManager.__exit__
詳細については、Context Manager Typesに関するドキュメントを参照してください。
私達はちょうど包む、これらの異なるケースを区別したい場合with
にはtry .. except
十分ではありません。次の例を検討してください(例ValueError
として使用しますが、もちろん他の例外タイプで置き換えることもできます)。
try:
with ContextManager():
BLOCK
except ValueError as err:
print(err)
ここでexcept
は、4つの異なる場所すべてで発生した例外をキャッチするため、それらを区別することはできません。コンテキストマネージャオブジェクトのインスタンス化をの外に移動with
すると、__init__
とを区別できます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
事実上、これはその__init__
部分に役立ちましたが、追加のセンチネル変数を追加して、with
開始されたボディが実行を開始したかどうかを確認できます(つまり__enter__
、他のものとの区別)。
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
トリッキーな部分から、例外の発信元を区別することであるBLOCK
と__exit__
の身体エスケープ例外ためwith
に渡されます__exit__
(参照、それをどのように処理するかを決めることができたドキュメントを)。ただし__exit__
、それ自体が発生した場合、元の例外は新しい例外に置き換えられます。これらのケースに対処するためexcept
に、の本体に一般的な句を追加して、with
他の方法では気付かれずに逃れる可能性のある例外を格納し、except
後で最も外側でキャッチされたものと比較することができます。BLOCK
またはそれ以外の__exit__
場合(__exit__
最も外側の真の値を返すことで例外を抑制する場合)except
実行されません)。
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)
PEP 343で言及されている同等のフォームを使用した代替アプローチ
PEP 343-"with"ステートメントは、ステートメントの同等の "non-with"バージョンを指定しますwith
。ここでは、さまざまなパーツを簡単にラップして、さまざまなtry ... except
潜在的なエラーソースを区別できます。
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)
通常、より単純なアプローチで十分です
このような特別な例外処理の必要性は非常にまれであり、通常は全体with
をtry ... except
ブロックでラップすることで十分です。特に、さまざまなエラーソースが異なる(カスタム)例外タイプで示されている場合(コンテキストマネージャはそれに応じて設計する必要があります)、それらを簡単に区別できます。例えば:
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
声明は、魔法のように、周囲の破損しないtry...except
声明を。