9

私のコードでは、デバイスを適切に開いたり閉じたりできる必要があるため、コンテキスト マネージャーを使用する必要があることがわかります。__enter__コンテキスト マネージャーは通常、とメソッドを持つクラスとして定義され__exit__ますが、コンテキスト マネージャーで使用するために関数を装飾する可能性もあるようです (最近の投稿別の良い例を参照してください ここ)。

次の (動作中の) コード スニペットでは、2 つの可能性を実装しています。コメント行を別の行と交換するだけです。

import time
import contextlib

def device():
    return 42

@contextlib.contextmanager
def wrap():
    print("open")
    yield device
    print("close")
    return

class Wrap(object):
    def __enter__(self):
        print("open")
        return device
    def __exit__(self, type, value, traceback):
        print("close")


#with wrap() as mydevice:
with Wrap() as mydevice:
    while True:
        time.sleep(1)
        print mydevice()

私が試しているのは、コードを実行して で停止することCTRL-Cです。Wrapコンテキストマネージャーでクラスを使用すると、__exit__メソッドは期待どおりに呼び出されます (端末にテキスト「close」が出力されます)が、wrap関数で同じことを試みると、テキスト「close」が出力されませんターミナル。

私の質問: コード スニペットに問題がありますか、何か不足していますか、それともprint("close")装飾された関数で行が呼び出されないのはなぜですか?

4

1 に答える 1

17

のドキュメントの例は、contextmanagerやや誤解を招く可能性があります。関数の後の部分は、コンテキスト マネージャー プロトコルの にyield実際には対応していません。__exit__ドキュメントの重要なポイントは次のとおりです。

ブロック内で未処理の例外が発生した場合、生成器内で生成された時点で例外が再発生します。したがって、try...except...finallyステートメントを使用してエラー (存在する場合) をトラップしたり、クリーンアップを確実に実行したりすることができます。

そのため、contextmanager で装飾された関数で例外を処理する場合は、tryをラップして独自yieldに例外を処理し、 でクリーンアップ コードを実行するfinally(または で例外をブロックし、 のexcept後でクリーンアップを実行するtry/except) 必要があります。例えば:

@contextlib.contextmanager
def cm():
    print "before"
    exc = None
    try:
        yield
    except Exception, exc:
        print "Exception was caught"
    print "after"
    if exc is not None:
        raise exc

>>> with cm():
...     print "Hi!"
before
Hi!
after

>>> with cm():
...     print "Hi!"
...     1/0
before
Hi!
Exception was caught
after

このページには、有益な例も示されています。

于 2013-03-16T08:30:53.977 に答える