28.7. contextlib --- with 文コンテキスト用ユーティリティ

バージョン 2.5 で追加.

ソースコード: Lib/contextlib.py


このモジュールは with 文に関わる一般的なタスクのためのユーティリティを提供します。詳しい情報は、 コンテキストマネージャ型with文とコンテキストマネージャ を参照してください。

提供されている関数:

contextlib.contextmanager(func)

この関数は with 文コンテキストマネージャのファクトリ関数を定義するために利用できる デコレータ です。新しいクラスや __enter__()__exit__() メソッドを別々に定義しなくても、ファクトリ関数を定義することができます。

While many objects natively support use in with statements, sometimes a resource needs to be managed that isn't a context manager in its own right, and doesn't implement a close() method for use with contextlib.closing

An abstract example would be the following to ensure correct resource management:

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つ yield しなければなりません。 with 文の as 節が存在するなら、その値は as 節のターゲットへ束縛されることになります。

ジェネレータが yield を実行した箇所で with 文のネストされたブロックが実行されます。ブロックから抜けた後でジェネレータは再開されます。ブロック内で処理されない例外が発生した場合は、ジェネレータ内部の yield を実行した箇所で例外が再送出されます。なので、(もしあれば) エラーを捕捉したり、クリーンアップ処理を確実に実行したりするために、try...except...finally 構文を使用できます。例外を捕捉する目的が、(完全に例外を抑制してしまうのではなく) 単に例外のログをとるため、もしくはあるアクションを実行するためなら、ジェネレータはその例外を再送出しなければなりません。例外を再送出しない場合、ジェネレータのコンテキストマネージャは with 文に対して例外が処理されたことを示し、with 文の直後の文から実行を再開します。

contextlib.nested(mgr1[, mgr2[, ...]])

複数のコンテキストマネージャを一つのネストされたコンテキストマネージャへ結合します。

この関数は、 with 文にマルチマネージャー形式ができたために非推奨になりました。

この関数の with 文のマルチマネージャー形式に対する唯一の利点は、引数のアンパックによって、次の例のように可変数個のコンテキストマネージャーを扱えることです。

from contextlib import nested

with nested(*managers):
    do_something()

ネストされたコンテキストマネージャのうちのいずれかの __exit__() メソッドが例外を抑制すべきと判断した場合、外側にある残りのすべてのコンテキストマネージャに例外情報が渡されないということに注意してください。同様に、ネストされたコンテキストマネージャのうちのいずれかの __exit__() メソッドが例外を送出したならば、それ以前の例外状態は失われ、新しい例外が外側にある残りのすべてのコンテキストマネージャの __exit__() メソッドに渡されます。一般的に __exit__() メソッドが例外を送出することは避けるべきであり、特に渡された例外を再送出すべきではありません。

この関数が非推奨になった理由に、2つの大きな問題があります。 1つ目は、全てのコンテキストマネージャーが関数が呼び出される前に構築されることです。内側のコンテキストマネージャーの __new__()__init__() メソッドは外側のコンテキストマネージャーの内側に入っていません。つまり、例えば nested() を2つのファイルを開くために利用した場合、 2つめのファイルを開くのに失敗すると1つめのファイルが正しく close されないというプログラムエラーになります。

2つ目の問題は、内側のコンテキストマネージャーの1つの __enter__() メソッドが例外を発生させたときに、外側のコンテキストマネージャーの __exit__() メソッドがその例外を捕まえて抑制させてしまうことで、この場合に with の body 部分の実行がスキップされるのではなく RuntimeError が発生する恐れがあります。

可変数個のコンテキストマネージャーのネストをサポートしなければならない場合、 warnings モジュールを利用してこの関数が発生させる DeprecationWarning を抑制するか、この関数を参考にしてアプリケーション独自の実装をすることができます。

バージョン 2.7 で非推奨: with 文がこの関数の機能を(この関数と違って奇妙なエラーを発生させることなしに) 直接サポートしました。

contextlib.closing(thing)

ブロックの完了時に thing を close するコンテキストマネージャを返します。これは基本的に以下と等価です:

from contextlib import contextmanager

@contextmanager
def closing(thing):
    try:
        yield thing
    finally:
        thing.close()

そして、明示的に page を close する必要なしに、次のように書くことができます:

from contextlib import closing
import urllib

with closing(urllib.urlopen('http://www.python.org')) as page:
    for line in page:
        print line

page を明示的に close する必要は無く、エラーが発生した場合でも、 with ブロックを出るときに page.close() が呼ばれます。

参考

PEP 343 - "with" ステートメント

仕様、背景、および、Python with 文の例。