web-dev-qa-db-ja.com

pythonコンテキストマネージャ内の例外を安全に処理する方法

with内の例外は__exit__が正しく呼び出されないことを読んだと思います。このメモに誤りがある場合は、私の無知をご容赦ください。

したがって、ここにいくつかの疑似コードがあります。私の目標は、__enter__が開始日時を記録してロックIDを返し、__exit__が終了日時を記録してロックを解放するロックコンテキストを使用することです。

def main():
    raise Exception

with cron.lock() as lockid:
    print('Got lock: %i' % lockid)
    main()

安全に既存のコンテキストに加えて、エラーを発生させるにはどうすればよいですか?

注:予期される例外だけでなく、例外が発生しても安全に終了したいので、この疑似コードで意図的にベース例外を発生させます。

注:代替/標準の同時実行防止方法は関係ありません。この知識を一般的なコンテキスト管理に適用したいと思います。異なるコンテキストに異なる癖があるかどうかわかりません。

PS。 finallyブロックは関連していますか?

18
ThorSummoner

___exit___メソッド通常どおり呼び出されますコンテキストマネージャが例外によって壊れた場合。実際、___exit___に渡されるパラメーターはすべて、このケースの処理に関係しています。 the docs から:

object.__exit__(self, exc_type, exc_value, traceback)

このオブジェクトに関連するランタイムコンテキストを終了します。パラメータは、コンテキストが終了する原因となった例外を示します。コンテキストが例外なく終了した場合、3つの引数はすべてNoneになります。

例外が提供され、メソッドが例外を抑制したい場合(つまり、例外が伝播されないようにする場合)、trueを返す必要があります。それ以外の場合、このメソッドの終了時に例外は通常どおり処理されます。

__exit__()メソッドは、渡された例外を再発生させないように注意してください。これは呼び出し側の責任です。

したがって、___exit___メソッドが実行され、デフォルトで、例外がコンテキストマネージャーを終了した後afterに再発生することがわかります。簡単なコンテキストマネージャを作成し、例外を付けてこれを解除することで、これを自分でテストできます。

_DummyContextManager(object):
    def __enter__(self):
        print('Entering...')
    def __exit__(self, exc_type, exc_value, traceback):
        print('Exiting...')  
        # If we returned True here, any exception would be suppressed!

with DummyContextManager() as foo:
    raise Exception()
_

このコードを実行すると、必要なものがすべて表示されます(printはトレースバックの途中で終了する傾向があるため、順序が狂っている可能性があります)。

_Entering...
Exiting...
Traceback (most recent call last):
  File "C:\foo.py", line 8, in <module>
    raise Exception()
Exception
_
28
Henry Keiter

@contextlib.contextmanagerを使用する際のベストプラクティスは、上記の回答からははっきりしませんでした。 @ BenUsman からのコメントで link をフォローしました。

コンテキストマネージャを作成している場合は、yieldtry-finallyブロックでラップする必要があります。

from contextlib import contextmanager

@contextmanager
def managed_resource(*args, **kwds):
    # Code to acquire resource, e.g.:
    resource = acquire_resource(*args, **kwds)
    try:
        yield resource
    finally:
        # Code to release resource, e.g.:
        release_resource(resource)

>>> with managed_resource(timeout=3600) as resource:
...     # Resource is released at the end of this block,
...     # even if code in the block raises an exception
1
Daniel Farrell