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


293

残念ながら、Pythonの「with」ステートメントの例外を処理する方法がわかりません。コードがある場合:

with open("a.txt") as f:
    print f.readlines()

somehingを実行するために、「ファイルが見つかりません」という例外を本当に処理したいと思っています。でも書けない

with open("a.txt") as f:
    print f.readlines()
except:
    print 'oops'

書けない

with open("a.txt") as f:
    print f.readlines()
else:
    print 'oops'

「with」をtry / exceptステートメントで囲むと、それ以外では機能しません。例外は発生しません。「with」ステートメント内の失敗をPythonicの方法で処理するにはどうすればよいですか?


「try / exceptステートメントで「with」を囲んでも機能しない:例外が発生しない」とはどういう意味ですか?with声明は、魔法のように、周囲の破損しないtry...except声明を。
Aran-Fey

4
興味深いことに、Javaのtry-with-resourcesステートメントは、このユースケースを正確にサポートします。docs.oracle.com/javase/tutorial/essential/exceptions/...
Nayuki

回答:


256
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'

open呼び出しと実際のコードのエラーの処理を変えたい場合は、次のようにします。

try:
    f = open('foo.txt')
except IOError:
    print('error')
else:
    with f:
        print f.readlines()

3
stackoverflow.com/questions/5205811/…に記載されているように、ここのtryブロックは非常に広すぎます。コンテキストマネージャの作成時の例外とwithステートメントの本文の例外は区別されないため、すべてのユースケースで有効なソリューションとは限りません。
ncoghlan

@ncoghlanただし、try...except内部withにブロックを追加して、とは何の関係もない例外のソースに近づけることができますopen()
rbaleksandar

1
@rbaleksandar私が正しく思い出した場合、私のコメントは回答の最初の例を厳密に参照していたため、withステートメント全体がtry / exceptブロック内にあります(したがって、内部try / expectブロックがある場合でも、エスケープさせる例外はすべてまだ外側のものを打つ)。ダグラスはその後、その区別が重要である場合に対処するために2番目の例を追加しました。
ncoghlan 2017年

3
この例では、ファイルは閉じられますか?「with」の範囲外で開かれたのでお願いします。
マイクコリンズ

6
@MikeCollins「with」を終了すると、「with」の前にファイルが開いていても、開いているファイルが閉じます。
user7938784

75

withステートメントを利用してこれを行うための「Python的」な最良の方法は、ステートメントの背景を提供するPEP 343の例#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")

38
私はそれが好きですが、それは少し黒魔術のように感じます。それは読者にとって完全に明白ではありません
Paul Seeb

5
@PaulSeebなぜそれを定義し、必要なときに毎回それを行わないようにしないのですか?これはアプリケーションレベルで定義され、他のコンテキストマネージャと同じくらい魔法のようです。withステートメントを使用している人は、それを明確に理解するだろうと思います(関数の名前も、気に入らない場合は、より表現力を高めるかもしれません)。「with」ステートメント自体は、この方法で機能するように設計されており、コードの「安全な」ブロックを定義し、チェック機能をコンテキストマネージャに委任します(コードをより明確にするため)。

9
このすべての問題は、ユーザーコードにfinallyブロックを記述しないことによるものです。私たちは、withステートメントの長い誇大広告の症状に苦しんでいると思い始めています。
jgomo3

1
Pythonで例外を処理する最良の方法は、例外をキャッチして返す関数を記述することです。マジ?例外を処理するpythonicの方法は、try...exceptステートメントを使用することです。
Aran-Fey

58

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

withステートメントは、Python 2.6以降__future__インポートなしで使用できます。早くも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:
    with open("a.txt") as f:
        print(f.readlines())
except Exception as error: 
    print('oops')

そして、標準のボイラープレート:except:キャッチするベアとBaseException他のすべての可能な例外と警告を使用しないでください。少なくともと同じくらい具体的にしてください。Exceptionこのエラーの場合は、おそらくキャッチしてくださいIOError。処理する準備ができているエラーのみをキャッチします。

したがって、この場合は、次のようにします。

>>> try:
...     with open("a.txt") as f:
...         print(f.readlines())
... except IOError as error: 
...     print('oops')
... 
oops

2

複合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)

通常、より単純なアプローチで十分です

このような特別な例外処理の必要性は非常にまれであり、通常は全体withtry ... 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__
    ...
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.