残念なことに、python 'with'ステートメントの例外を処理する方法がわかりません。コードがある場合:
with open("a.txt") as f:
print f.readlines()
私は本当に何かをするために「ファイルが見つからない例外」を処理したい。しかし、私は書くことができません
with open("a.txt") as f:
print f.readlines()
except:
print 'oops'
書けない
with open("a.txt") as f:
print f.readlines()
else:
print 'oops'
try/exceptステートメントで「with」を囲むことは機能しません。例外は発生しません。 Pythonの方法で 'with'ステートメント内のエラーを処理するにはどうすればよいですか?
from __future__ import with_statement
try:
with open( "a.txt" ) as f :
print f.readlines()
except EnvironmentError: # parent of IOError, OSError *and* WindowsError where available
print 'oops'
オープンコールと動作中のコードのエラーに対して異なる処理が必要な場合は、次のようにします。
try:
f = open('foo.txt')
except IOError:
print('error')
else:
with f:
print f.readlines()
with
ステートメントを活用してこれを行う最適な「Pythonの」方法は、 PEP 34 の例#6にリストされており、ステートメントの背景を示しています。
@contextmanager
def opened_w_error(filename, mode="r"):
try:
f = open(filename, mode)
except IOError, err:
yield None, err
else:
try:
yield f, None
finally:
f.close()
次のように使用します。
with opened_w_error("/etc/passwd", "a") as (f, err):
if err:
print "IOError:", err
else:
f.write("guido::0:0::/:/bin/sh\n")
Python 'with'ステートメントの使用中に例外をキャッチする
Withステートメントは、__future__
import Python 2.6以降 なしで使用できます。次のように 初期のPython 2.5 として取得できます(ただし、この時点でアップグレードする必要があります!)。
from __future__ import with_statement
修正するのに最も近いものは次のとおりです。あなたはほとんどそこにいますが、with
にはexcept
句がありません:
with open("a.txt") as f: print(f.readlines()) except: # <- with doesn't have an except clause. print('oops')
コンテキストマネージャの__exit__
メソッドは、False
を返す場合、終了時にエラーを再発生させます。 True
を返す場合、それを抑制します。 open
ビルトインの__exit__
はTrue
を返さないので、ブロックを除き、tryでネストする必要があります。
try:
with open("a.txt") as f:
print(f.readlines())
except Exception as error:
print('oops')
そして、標準的な定型文:BaseException
および他のすべての可能な例外と警告をキャッチする裸のexcept:
を使用しないでください。少なくともException
と同じくらい具体的にしてください。このエラーについては、おそらくIOError
をキャッチしてください。処理する準備ができているエラーのみをキャッチします。
この場合、次のようにします。
>>> try:
... with open("a.txt") as f:
... print(f.readlines())
... except IOError as error:
... print('oops')
...
oops
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
後で-それらが同じ場合、これはOriginが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-"with"ステートメント は、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)
このような特別な例外処理の必要性は非常にまれで、通常はtry ... except
ブロックでwith
全体をラップするだけで十分です。特に、さまざまなエラーソースが異なる(カスタム)例外タイプによって示されている場合(コンテキストマネージャーはそれに応じて設計する必要があります)、簡単に区別できます。例えば:
try:
with ContextManager():
BLOCK
except InitError: # raised from __init__
...
except AcquireResourceError: # raised from __enter__
...
except ValueError: # raised from BLOCK
...
except ReleaseResourceError: # raised from __exit__
...