web-dev-qa-db-ja.com

Python 'with'ステートメントの使用中に例外をキャッチする

残念なことに、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'ステートメント内のエラーを処理するにはどうすればよいですか?

239
grigoryvp
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()
219
Douglas Leeder

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")
63
user765144

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
49
Aaron Hall

複合withステートメントから発生した例外の考えられる起源を区別する

withステートメントで発生する例外を区別することは、異なる場所で発生する可能性があるため、注意が必要です。例外は、次の場所(またはその中で呼び出される関数)のいずれかから発生する可能性があります。

  • ContextManager.__init__
  • ContextManager.__enter__
  • withの本文
  • ContextManager.__exit__

詳細については、 Context Manager Types に関するドキュメントを参照してください。

これらの異なるケースを区別したい場合、withtry .. 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に記載されている同等の形式を使用した代替アプローチ

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__
    ...
1
a_guest