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" 文の例。
