"contextlib" --- "with" 文コンテキスト用ユーティリティ
******************************************************

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

======================================================================

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


ユーティリティ
==============

以下の関数とクラスを提供しています:

class contextlib.AbstractContextManager

   "__enter__()" と "__exit__()" を実装するクラスのための *abstract
   base class* です。 "__enter__()" には "self" を返すデフォルトの実装
   があり、"__exit__()" はデフォルトで "None" を返す抽象メソッドです。
   コンテキストマネージャ型 の定義も参照してください。

   Added in version 3.6.

class contextlib.AbstractAsyncContextManager

   "__aenter__()" と "__aexit__()" を実装するクラスのための *abstract
   base class* です。 "__aenter__()" には "self" を返すデフォルトの実
   装があり、"__aexit__()" はデフォルトで "None" を返す抽象メソッドで
   す。 非同期コンテキストマネージャ (Asynchronous Context Manager) の
   定義も参照してください。

   Added in version 3.7.

@contextlib.contextmanager

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

   多くのオブジェクトは with 文での使用をサポートしていますが、コンテ
   キストマネージャではなく、"contextlib.closing" で使うための
   "close()" メソッドも実装していないリソースを管理する必要が生じるこ
   ともあります。

   リソースを正しく管理するよう保証する抽象的な例は以下のようなもので
   しょう:

      from contextlib import contextmanager

      @contextmanager
      def managed_resource(*args, **kwds):
          # リソースを確保するコードの例:
          resource = acquire_resource(*args, **kwds)
          try:
              yield resource
          finally:
              # リソースを解放するコードの例:
              release_resource(resource)

   このとき、関数は次のように使うことができます:

      >>> with managed_resource(timeout=3600) as resource:
      ...     # ブロック内のコードが例外を送出しても、
      ...     # ブロックの終わりでリソースは解放されます

   デコレート対象の関数は呼び出されたときに *ジェネレータ*-イテレータ
   を返す必要があります。このイテレータは必ず値を1つ yield しなければ
   なりません。 "with" 文の "as" 節が存在するなら、その値は as 節のタ
   ーゲットへ束縛されることになります。

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

   "@contextmanager" は "ContextDecorator" を使用するため、作成したコ
   ンテキストマネージャは "with" 文でもデコレータとしても使用できます
   。 デコレータとして使う場合は、関数を呼び出すたびに、新しいジェネレ
   ータインスタンスが暗黙に作成されます (これにより、"@contextmanager"
   が作成する、本来は一度しか使えないコンテキストマネージャも、デコレ
   ータとして使うための複数回の呼び出しをサポートするという要件を満た
   せます)。

   バージョン 3.2 で変更: "ContextDecorator" の使用。

@contextlib.asynccontextmanager

   "@~contextlib.contextmanager" に似ていますが、非同期コンテキストマ
   ネージャ を作成します。

   この関数は "async with" 文のための非同期コンテキストマネージャのフ
   ァクトリ関数を定義するために利用できるデコレータ (*decorator*) です
   。新しいクラスや  "__aenter__()" と "__aexit__()" メソッドを個別に
   定義する必要はありません。このデコレータは非同期ジェネレータ
   (*asynchronous generator*) 関数に適用しなければなりません。

   簡単な例:

      from contextlib import asynccontextmanager

      @asynccontextmanager
      async def get_connection():
          conn = await acquire_db_connection()
          try:
              yield conn
          finally:
              await release_db_connection(conn)

      async def get_all_users():
          async with get_connection() as conn:
              return conn.query('SELECT ...')

   Added in version 3.7.

   "@asynccontextmanager" で定義したコンテキストマネージャは、デコレー
   タとしても "async with" 文でも使用できます。

      import time
      from contextlib import asynccontextmanager

      @asynccontextmanager
      async def timeit():
          now = time.monotonic()
          try:
              yield
          finally:
              print(f'it took {time.monotonic() - now}s to run')

      @timeit()
      async def main():
          # ... 非同期のコード ...

   デコレータとして使う場合は、関数を呼び出すたびに、新しいジェネレー
   タインスタンスが暗黙に作成されます。 これにより、
   "@asynccontextmanager" が作成する、本来は一度しか使えないコンテキス
   トマネージャも、デコレータとして使うための複数回の呼び出しをサポー
   トするという要件を満たせます。

   バージョン 3.10 で変更: "@asynccontextmanager" で作成した非同期コン
   テキストマネージャをデコレータとして使用できるようになりました。

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
      from urllib.request import urlopen

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

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

   注釈:

     リソースを管理するほとんどの型は *context manager* プロトコルをサ
     ポートし、"with" 文を抜けるときに *thing* を閉じます。 そのため、
     "closing()" はコンテキストマネージャをサポートしないサードパーテ
     ィー製の型で特に役立ちます。 "urlopen()" は通常、コンテキストマネ
     ージャとして使用するため、この例は説明のためのものにすぎません。

contextlib.aclosing(thing)

   ブロックの完了時に *thing* の "aclose()" メソッドを呼び出すような非
   同期コンテキストマネージャを返します。これは基本的に以下と等価です:

      from contextlib import asynccontextmanager

      @asynccontextmanager
      async def aclosing(thing):
          try:
              yield thing
          finally:
              await thing.aclose()

   重要なことは、たとえば次の例のように "break" や例外によって早期にブ
   ロックが終了した場合に、 "aclosing()" は非同期ジェネレータの決定論
   的なクリーンアップをサポートすることです:

      from contextlib import aclosing

      async with aclosing(my_generator()) as values:
          async for value in values:
              if value == 42:
                  break

   このパターンは、ジェネレータの非同期な終了のコードがイテレーション
   処理と同じコンテキストの中で実行されることを保証します (すなわち例
   外とコンテキスト変数は期待通りに動作し、またジェネレータが依存する
   タスクの寿命が尽きたあとに終了のコードが実行されることもありません)
   。

   Added in version 3.10.

contextlib.nullcontext(enter_result=None)

   "__enter__()" から *enter_result* を返し、それ以外は何もしないコン
   テキストマネージャを返します。 省略可能なコンテキストマネージャの代
   役として使用することを意図しています。 例えば次のように使います。

      def myfunction(arg, ignore_exceptions=False):
          if ignore_exceptions:
              # suppress を使ってすべての例外を無視します。
              cm = contextlib.suppress(Exception)
          else:
              # 例外を無視せず、cm は何も行いません。
              cm = contextlib.nullcontext()
          with cm:
              # 何らかの処理

   *enter_result* を使った例です:

      def process_file(file_or_path):
          if isinstance(file_or_path, str):
              # 文字列の場合はファイルを開きます
              cm = open(file_or_path)
          else:
              # ファイルを閉じる責任は呼び出し側にあります
              cm = nullcontext(file_or_path)

          with cm as file:
              # ファイルを処理します

   非同期コンテキストマネージャ の代役として使うこともできます:

      async def send_http(session=None):
          if not session:
              # HTTP セッションがなければ aiohttp で作成します
              cm = aiohttp.ClientSession()
          else:
              # セッションを閉じる責任は呼び出し側にあります
              cm = nullcontext(session)

          async with cm as session:
              # session を使って HTTP リクエストを送信します

   Added in version 3.7.

   バージョン 3.10 で変更: 非同期コンテキストマネージャ (*asynchronous
   context manager*) のサポートが追加されました。

contextlib.suppress(*exceptions)

   "with" 文の内部で指定された例外の発生を抑えるコンテキストマネージャ
   を返します。 "with" 文の後に続く最初の文から処理が再開されます。

   ほかの完全に例外を抑制するメカニズム同様、このコンテキストマネージ
   ャは、黙ってプログラム実行を続けることが正しいことであるとわかって
   いる、非常に限定的なエラーをカバーする以上の使い方はしてはいけませ
   ん。

   例えば:

      from contextlib import suppress

      with suppress(FileNotFoundError):
          os.remove('somefile.tmp')

      with suppress(FileNotFoundError):
          os.remove('someotherfile.tmp')

   これは以下と等価です:

      try:
          os.remove('somefile.tmp')
      except FileNotFoundError:
          pass

      try:
          os.remove('someotherfile.tmp')
      except FileNotFoundError:
          pass

   このコンテキストマネージャは 再入可能(リエントラント) です。

   "with" ブロック内のコードが "BaseExceptionGroup" を送出した場合、抑
   制対象の例外はグループから取り除かれます。 グループ内の抑制されなか
   った例外は、元のグループの "derive()" メソッドで作成した新しいグル
   ープにまとめて再送出されます。

   Added in version 3.4.

   バージョン 3.12 で変更: "suppress" は、"BaseExceptionGroup" の一部
   として送出された例外の抑制をサポートするようになりました。

contextlib.redirect_stdout(new_target)

   "sys.stdout" を一時的に別の *file object* にリダイレクトするコンテ
   キストマネージャです。

   このツールは、出力先が "sys.stdout" に固定されている既存の関数やク
   ラスを柔軟に使えるようにします。

   例えば、"help()" の出力は通常 *sys.stdout* に送られます。 出力先を
   "io.StringIO" オブジェクトにリダイレクトすれば、出力を文字列として
   取得できます。 置き換え先のストリームは "__enter__()" メソッドから
   返されるので、"with" 文のターゲットとして使用できます。

      with redirect_stdout(io.StringIO()) as f:
          help(pow)
      s = f.getvalue()

   "help()" の出力をディスク上のファイルに送るためには、出力を通常のフ
   ァイルにリダイレクトします:

      with open('help.txt', 'w') as f:
          with redirect_stdout(f):
              help(pow)

   "help()" の出力を標準エラー出力 (*sys.stderr*) に送るには以下のよう
   にします:

      with redirect_stdout(sys.stderr):
          help(pow)

   "sys.stdout" のシステム全体にわたる副作用により、このコンテキストマ
   ネージャはライブラリコードやマルチスレッドアプリケーションでの使用
   には適していません。また、サブプロセスの出力に対しても効果がありま
   せん。そのような制限はありますが、それでも多くのユーティリティスク
   リプトに対して有用なアプローチです。

   このコンテキストマネージャは 再入可能(リエントラント) です。

   Added in version 3.4.

contextlib.redirect_stderr(new_target)

   "redirect_stdout()" に似ていますが、グローバルな "sys.stderr" を別
   の *file object* にリダイレクトします。

   このコンテキストマネージャは 再入可能(リエントラント) です。

   Added in version 3.5.

contextlib.chdir(path)

   現在の作業ディレクトリを変更するパラレル非安全なコンテキストマネー
   ジャです。グローバルな状態である作業ディレクトリを変更するため、ほ
   とんどのマルチスレッドまたは非同期のコンテキストに対する利用は適切
   ではありません。また、プログラムの実行権限を一時的に放棄するジェネ
   レータのような、直線的でないコードを実行する場合も適切ではありませ
   ん -- 明確に必要でないかぎり、このコンテキストマネージャがアクティ
   ブな状態で yield すべきではありません。

   これは "chdir()" の単純なラッパーで、コンテキストに入るときに現在の
   作業ディレクトリを変更し、終了時に元の作業ディレクトリを復元します
   。

   このコンテキストマネージャは 再入可能(リエントラント) です。

   Added in version 3.11.

class contextlib.ContextDecorator

   コンテキストマネージャをデコレータとしても使用できるようにする基底
   クラスです。

   "ContextDecorator" を継承するコンテキストマネージャは、通常どおり
   "__enter__()" と "__exit__()" を実装する必要があります。 デコレータ
   として使用した場合も、"__exit__" で任意に例外処理を行えます。

   "@contextmanager" は "ContextDecorator" を使用するため、この機能を
   自動的に利用できます。

   "ContextDecorator" の例:

      from contextlib import ContextDecorator

      class mycontext(ContextDecorator):
          def __enter__(self):
              print('Starting')
              return self

          def __exit__(self, *exc):
              print('Finishing')
              return False

   このとき、クラスは次のように使うことができます:

      >>> @mycontext()
      ... def function():
      ...     print('The bit in the middle')
      ...
      >>> function()
      Starting
      The bit in the middle
      Finishing

      >>> with mycontext():
      ...     print('The bit in the middle')
      ...
      Starting
      The bit in the middle
      Finishing

   これは次のような形のコードに対するシンタックスシュガーになります:

      def f():
          with cm():
              # 処理を行います

   "ContextDecorator" を使うと代わりに次のように書けます:

      @cm()
      def f():
          # 処理を行います

   デコレーターを使うと、"cm" が関数の一部ではなく全体に適用されている
   ことが明確になります (インデントレベルを1つ節約できるのもメリットで
   す)。

   すでに基底クラスを持っているコンテキストマネージャーも、
   "ContextDecorator" を mixin クラスとして利用することで拡張できます:

      from contextlib import ContextDecorator

      class mycontext(ContextBaseClass, ContextDecorator):
          def __enter__(self):
              return self

          def __exit__(self, *exc):
              return False

   注釈:

     デコレートされた関数が複数回呼び出せるように、内部のコンテキスト
     マネージャーは複数の "with" 文に対応する必要があります。そうでな
     いなら、明示的な "with" 文を関数内で利用するべきです。

   デコレートする呼び出し可能オブジェクトがジェネレータ関数、コルーチ
   ン関数、非同期ジェネレータ関数の場合、返されるラッパーも同じ種類に
   なります。 コンテキストマネージャは、ジェネレータやコルーチンオブジ
   ェクトを作成する呼び出しの間だけでなく、反復や await の間ずっと開い
   たままになります。 ラップされたジェネレータと非同期ジェネレータは、
   反復の終了時に "closing()" や "aclosing()" を使った場合と同様に明示
   的に閉じられます。

   注釈:

     非同期ジェネレータのラッパーは、"async for" を使って各値を再度
     yield します。 "asend()" で送った値や "athrow()" で投げ込んだ例外
     は、ラップされたジェネレータには転送されません。

   Added in version 3.2.

   バージョン 3.15 で変更: ジェネレータ関数、コルーチン関数、非同期ジ
   ェネレータ関数をデコレートすると、反復や await の間もコンテキストマ
   ネージャを開いたままにするようになりました。 以前は、ジェネレータや
   コルーチンオブジェクトを作成するとすぐにコンテキストマネージャを終
   了していました。

class contextlib.AsyncContextDecorator

   "ContextDecorator" に似ていますが、コンテキストマネージャへの出入り
   に "async with" を使います。 このクラスは、コルーチン関数と非同期ジ
   ェネレータ関数のデコレートに使ってください。 返されるラッパーも同じ
   種類になります。

   注釈:

     同期関数と同期ジェネレータも受け付けますが、ラッパーは常に非同期
     になるため、デコレートした呼び出し可能オブジェクトは await するか
     、"async for" で反復する必要があります。 呼び出し方を変更する意図
     がない場合は、代わりに "ContextDecorator" を使ってください。

   "AsyncContextDecorator" の使用例:

      from asyncio import run
      from contextlib import AsyncContextDecorator

      class mycontext(AsyncContextDecorator):
          async def __aenter__(self):
              print('Starting')
              return self

          async def __aexit__(self, *exc):
              print('Finishing')
              return False

   このとき、クラスは次のように使うことができます:

      >>> @mycontext()
      ... async def function():
      ...     print('The bit in the middle')
      ...
      >>> run(function())
      Starting
      The bit in the middle
      Finishing

      >>> async def function():
      ...    async with mycontext():
      ...         print('The bit in the middle')
      ...
      >>> run(function())
      Starting
      The bit in the middle
      Finishing

   Added in version 3.10.

   バージョン 3.15 で変更: 非同期ジェネレータ関数をデコレートすると、
   反復の間もコンテキストマネージャを開いたままにするようになりました
   。 以前は、ジェネレータオブジェクトを作成するとすぐにコンテキストマ
   ネージャを終了していました。 同期関数と同期ジェネレータ関数も受け付
   けるようになり、その場合は非同期のラッパーを返します。

class contextlib.ExitStack

   他の、特にオプションであったり入力に依存するようなコンテキストマネ
   ージャーやクリーンアップ関数を動的に組み合わせるためのコンテキスト
   マネージャーです。

   例えば、複数のファイルを1つの with 文で簡単に扱うことができます:

      with ExitStack() as stack:
          files = [stack.enter_context(open(fname)) for fname in filenames]
          # リストの後半にあるファイルを開く際に例外が発生しても、
          # 開いたすべてのファイルは with 文の終わりで
          # 自動的に閉じられます

   "__enter__()" メソッドは "ExitStack" インスタンスを返すだけで、追加
   の処理は行いません。

   各インスタンスは登録されたコールバックのスタックを管理し、インスタ
   ンスが (明示的に、あるいは "with" 文の終わりに暗黙的に)  close され
   るときに逆順でそれを呼び出します。コンテキストスタックのインスタン
   スが暗黙的にガベージコレクトされたときには callback は呼び出され **
   ません** 。

   このスタックモデルは、(file オブジェクトのように) "__init__" メソッ
   ドでリソースを確保するコンテキストマネージャーを正しく扱うためのも
   のです。

   登録されたコールバックが登録の逆順で実行されるので、複数のネストさ
   れた "with" 文を利用するのと同じ振る舞いをします。これは例外処理に
   も適用されます。内側のコールバックが例外を抑制したり置き換えたりし
   た場合、外側のコールバックには更新された状態に応じた引数が渡されま
   す。

   これは正しく exit callback の stack を巻き戻すための、比較的低レベ
   ルな API です。アプリケーション独自のより高レベルなコンテキストマネ
   ージャーを作るための基板として使うのに適しています。

   Added in version 3.3.

   enter_context(cm)

      新しいコンテキストマネージャに入り、その "__exit__()" メソッドを
      コールバックスタックに追加します。 戻り値は、コンテキストマネー
      ジャ自身の "__enter__()" メソッドの結果です。

      コンテキストマネージャーは、普段 "with" 文で利用された時と同じよ
      うに、例外を抑制することができます。

      バージョン 3.11 で変更: *cm* がコンテキストマネージャでなかった
      場合、 "AttributeError" の代わりに "TypeError" 例外を送出します
      。

      バージョン 3.15 で変更: "__enter__()" と "__exit__()" に任意のデ
      スクリプタを使用できるようになりました。

   push(exit)

      コンテキストマネージャの "__exit__()" メソッドをコールバックスタ
      ックに追加します。

      "__enter__" は呼び出され *ない* ため、このメソッドを使えば、
      "__enter__()" の実装の一部を、コンテキストマネージャ自身の
      "__exit__()" メソッドによる終了処理の対象にできます。

      コンテキストマネージャでないオブジェクトが渡された場合、このメソ
      ッドは、そのオブジェクトをコンテキストマネージャの "__exit__()"
      メソッドと同じシグネチャを持つコールバックと見なし、コールバック
      スタックに直接追加します。

      これらのコールバックは、真の値を返すことで、コンテキストマネージ
      ャの "__exit__()" メソッドと同様に例外を抑制できます。

      この関数はデコレータとしても使えるように、受け取ったオブジェクト
      をそのまま返します。

      バージョン 3.15 で変更: "__exit__()" に任意のデスクリプタを使用
      できるようになりました。

   callback(callback, /, *args, **kwds)

      任意の関数と引数を受け取り、コールバックスタックに追加します。

      他のメソッドと異なり、このメソッドで追加されたコールバックは例外
      を抑制しません (例外の詳細も渡されません)。

      この関数はデコレータとしても使えるように、受け取った callback を
      そのまま返します。

   pop_all()

      コールバックスタックを新しい "ExitStack" インスタンスに移して、
      それを返します。このメソッドは callback を実行しません。代わりに
      、新しい stack が (明示的に、あるいは "with" 文の終わりに暗黙的
      に) close されるときに実行されます。

      例えば、複数のファイルを "all or nothing" に開く処理を次のように
      書けます:

         with ExitStack() as stack:
             files = [stack.enter_context(open(fname)) for fname in filenames]
             # close メソッドを保持しますが、まだ呼び出しません。
             close_files = stack.pop_all().close
             # どれかのファイルを開くのに失敗すると、それまでに開いた
             # すべてのファイルが自動的に閉じられます。
             # すべてのファイルを
             # 開けた場合は、with 文が終わった後も開いたままになります。
             # その後、close_files() を明示的に呼んですべて閉じられます。

   close()

      すぐにコールバックスタックを巻き戻し、コールバック関数を登録の逆
      順に呼び出します。登録されたすべてのコンテキストマネージャーと終
      了 callback に、例外が起こらなかった場合の引数が渡されます。

class contextlib.AsyncExitStack

   "ExitStack" に似た 非同期コンテキストマネージャ です。スタック上で
   同期と非同期の両方のコンテキストマネージャの組み合わせをサポートし
   ます。また、後処理のためのコルーチンも持っています。

   "close()" メソッドは実装されていません。 代わりに "aclose()" を使用
   する必要があります。

   async enter_async_context(cm)

      "ExitStack.enter_context()" に似ていますが、非同期コンテキストマ
      ネージャを受け取ります。

      バージョン 3.11 で変更: *cm* が非同期コンテキストマネージャでな
      かった場合、 "AttributeError" の代わりに "TypeError" 例外を送出
      します。

      バージョン 3.15 で変更: "__aenter__()" と "__aexit__()" に任意の
      デスクリプタを使用できるようになりました。

   push_async_exit(exit)

      "ExitStack.push()" に似ていますが、非同期コンテキストマネージャ
      またはコルーチン関数を受け取ります。

      バージョン 3.15 で変更: "__aexit__()" に任意のデスクリプタを使用
      できるようになりました。

   push_async_callback(callback, /, *args, **kwds)

      "ExitStack.callback()" に似ていますが、コルーチン関数を受け取り
      ます。

   async aclose()

      "ExitStack.close()" に似ていますが、待機可能オブジェクトを適切に
      処理します。

   "@asynccontextmanager" の例の続きです。

      async with AsyncExitStack() as stack:
          connections = [await stack.enter_async_context(get_connection())
              for i in range(5)]
          # リストの後半で接続を開く際に例外が発生しても、
          # 開いたすべての接続は async with 文の終わりで
          # 自動的に解放されます。

   Added in version 3.7.


例とレシピ
==========

この節では、"contextlib" の提供するツールを効果的に使うための例やレシ
ピを紹介します。


可変数個のコンテキストマネージャーをサポートする
------------------------------------------------

"ExitStack" の第一のユースケースは、クラスのドキュメントにかかれている
通り、一つの "with" 文で可変数個のコンテキストマネージャーや他のクリー
ンアップ関数をサポートすることです。ユーザーの入力 (指定された複数個の
ファイルを開く場合など) に応じて複数個のコンテキストマネージャーが必要
となる場合や、いくつかのコンテキストマネージャーがオプションとなる場合
に、可変数個のコンテキストマネージャーが必要になります:

   with ExitStack() as stack:
       for resource in resources:
           stack.enter_context(resource)
       if need_special_resource():
           special = acquire_special_resource()
           stack.callback(release_special_resource, special)
       # 確保したリソースを使う操作を行います

上の例にあるように、 "ExitStack" はコンテキストマネージャープロトコル
をサポートしていないリソースの管理を "with" 文を使って簡単に行えるよう
にします。


"__enter__" メソッドからの例外をキャッチする
--------------------------------------------

"with" 文の本体やコンテキストマネージャの "__exit__()" メソッドからの
例外を誤って捕捉 *せずに*、"__enter__()" メソッドの実装からの例外だけ
を捕捉したい場合があります。 "ExitStack" を使えば、コンテキスト管理プ
ロトコルの手順を少し分離することで、これを実現できます。

   stack = ExitStack()
   try:
       x = stack.enter_context(cm)
   except Exception:
       # __enter__ の例外を処理します
   else:
       with stack:
           # 通常の場合を処理します

実際のところ、このようなコードが必要になるのならば、利用している API
側で "try"/"except"/"finally" 文を使った直接的なリソース管理インターフ
ェースを提供するべきです。しかし、すべての API がそのようによく設計さ
れているとは限りません。もしコンテキストマネージャーが提供されている唯
一のリソース管理APIであるなら、 "ExitStack" を使って "with" 文を使って
処理することができない様々なシチュエーションの処理をすることができます
。


"__enter__" 実装内のクリーンアップ
----------------------------------

"ExitStack.push()" のドキュメントで説明したとおり、このメソッドは
"__enter__()" の実装の後半で処理が失敗した場合に、すでに確保したリソー
スをクリーンアップするのに役立ちます。

次の例では、リソースの確保と開放の関数に加えて、オプションのバリデーシ
ョン関数を受け取るコンテキストマネージャーで、この方法を使ってコンテキ
ストマネージャープロトコルを提供しています:

   from contextlib import contextmanager, AbstractContextManager, ExitStack

   class ResourceManager(AbstractContextManager):

       def __init__(self, acquire_resource, release_resource, check_resource_ok=None):
           self.acquire_resource = acquire_resource
           self.release_resource = release_resource
           if check_resource_ok is None:
               def check_resource_ok(resource):
                   return True
           self.check_resource_ok = check_resource_ok

       @contextmanager
       def _cleanup_on_error(self):
           with ExitStack() as stack:
               stack.push(self)
               yield
               # 検証に成功し、例外は発生しませんでした。
               # したがって、リソースを保持し、
               # 呼び出し側に返します。
               stack.pop_all()

       def __enter__(self):
           resource = self.acquire_resource()
           with self._cleanup_on_error():
               if not self.check_resource_ok(resource):
                   msg = "Failed validation for {!r}"
                   raise RuntimeError(msg.format(resource))
           return resource

       def __exit__(self, *exc_details):
           # リソースの解放処理を重複して記述する必要はありません
           self.release_resource()


"try-finally" + flag 変数パターンを置き換える
---------------------------------------------

"try-finally" 文に、"finally" 句の内容を実行するかどうかを示すフラグ変
数を組み合わせたパターンを目にすることがあるかもしれません。一番シンプ
ルな (単に "except" 句を使うだけでは処理できない) ケースでは次のような
コードになります:

   cleanup_needed = True
   try:
       result = perform_operation()
       if result:
           cleanup_needed = False
   finally:
       if cleanup_needed:
           cleanup_resources()

"try" 文を使ったコードでは、セットアップとクリーンアップのコードが任意
の長さのコードで分離してしまうので、開発者やレビューアにとって問題にな
りえます。

"ExitStack" を使えば、代わりに "with" 文の終わりに実行されるコールバッ
クを登録し、後でそのコールバックをスキップするかどうかを決定できます:

   from contextlib import ExitStack

   with ExitStack() as stack:
       stack.callback(cleanup_resources)
       result = perform_operation()
       if result:
           stack.pop_all()

これにより、別途フラグ変数を用意せずに、意図したクリーンアップの動作を
あらかじめ明示できます。

もしあるアプリケーションがこのパターンを多用するのであれば、小さいヘル
パークラスを導入してよりシンプルにすることができます:

   from contextlib import ExitStack

   class Callback(ExitStack):
       def __init__(self, callback, /, *args, **kwds):
           super().__init__()
           self.callback(callback, *args, **kwds)

       def cancel(self):
           self.pop_all()

   with Callback(cleanup_resources) as cb:
       result = perform_operation()
       if result:
           cb.cancel()

もしリソースのクリーンアップが単体の関数にまとまってない場合でも、
"ExitStack.callback()" のデコレーター形式を利用してリソース開放処理を
宣言することができます:

   from contextlib import ExitStack

   with ExitStack() as stack:
       @stack.callback
       def cleanup_resources():
           ...
       result = perform_operation()
       if result:
           stack.pop_all()

デコレータープロトコルの使用上、このように宣言されたコールバック関数は
引数を取ることができません。その代わりに、リリースするリソースをクロー
ジャー変数としてアクセスできる必要があります。


コンテキストマネージャーを関数デコレーターとして使う
----------------------------------------------------

"ContextDecorator" はコンテキストマネージャーを通常の "with" 文に加え
て関数デコレーターとしても利用できるようにします。

例えば、関数やまとまった文を、そこに入った時と出た時の時間をトラックす
るロガーでラップしたい場合があります。そのために関数デコレーターとコン
テキストマネージャーを別々に書く代わりに、 "ContextDecorator" を継承す
ると1つの定義で両方の機能を提供できます:

   from contextlib import ContextDecorator
   import logging

   logging.basicConfig(level=logging.INFO)

   class track_entry_and_exit(ContextDecorator):
       def __init__(self, name):
           self.name = name

       def __enter__(self):
           logging.info('Entering: %s', self.name)

       def __exit__(self, exc_type, exc, exc_tb):
           logging.info('Exiting: %s', self.name)

このクラスのインスタンスはコンテキストマネージャーとしても利用でき:

   with track_entry_and_exit('widget loader'):
       print('Some time consuming activity goes here')
       load_widget()

また関数デコレーターとしても利用できます:

   @track_entry_and_exit('widget loader')
   def activity():
       print('Some time consuming activity goes here')
       load_widget()

コンテキストマネージャを関数デコレータとして使う場合には、もう 1 つ制
限があります。 "__enter__()" の戻り値にアクセスする方法がありません。
その値が必要な場合は、明示的な "with" 文を使う必要があります。

参考:

  **PEP 343** - "with" ステートメント
     Python の "with" 文の仕様、背景、および例が記載されています。


単回使用、再利用可能、およびリエントラントなコンテキストマネージャ
==================================================================

ほとんどのコンテキストマネージャは、 "with" 文の中で一度だけ使われるよ
うな場合に効果的になるように書かれています。これら単回使用のコンテキス
トマネージャは毎回新規に生成されなければなりません - それらを再利用し
ようとすると、例外を引き起こすか、正しく動作しません。

この共通の制限が意味することは、コンテキストマネージャは (上記すべての
使用例に示すとおり) 一般に "with" 文のヘッダ部分で直接生成することが推
奨されるということです。

ファイルオブジェクトは単回使用のコンテキストマネージャ有効に利用した例
です。最初の "with" 文によりファイルがクローズされ、それ以降そのファイ
ルオブジェクトに対するすべての IO 操作を防止します。

Context managers created using "@contextmanager" are also single use
context managers, and will complain about the underlying generator
failing to yield if an attempt is made to use them a second time:

   >>> from contextlib import contextmanager
   >>> @contextmanager
   ... def singleuse():
   ...     print("Before")
   ...     yield
   ...     print("After")
   ...
   >>> cm = singleuse()
   >>> with cm:
   ...     pass
   ...
   Before
   After
   >>> with cm:
   ...     pass
   ...
   Traceback (most recent call last):
       ...
   RuntimeError: generator didn't yield


リエントラントなコンテキストマネージャ
--------------------------------------

より洗練されたコンテキストマネージャには"リエントラント"なものがありま
す。そのようなコンテキストマネージャは、複数の "with" 文で使えるだけで
なく、同じコンテキストマネージャをすでに使っている "with" 文の *内部*
でも使うことができます。

"threading.RLock" はリエントラントなコンテキストマネージャの例であり、
また "suppress()", "redirect_stdout()", そして "chdir()" もリエントラ
ントです。以下はリエントラントな利用法の非常に単純な例です:

   >>> from contextlib import redirect_stdout
   >>> from io import StringIO
   >>> stream = StringIO()
   >>> write_to_stream = redirect_stdout(stream)
   >>> with write_to_stream:
   ...     print("This is written to the stream rather than stdout")
   ...     with write_to_stream:
   ...         print("This is also written to the stream")
   ...
   >>> print("This is written directly to stdout")
   This is written directly to stdout
   >>> print(stream.getvalue())
   This is written to the stream rather than stdout
   This is also written to the stream

リエントラントな性質の実例はお互いを呼び出しあう複数の関数を含んでいる
可能性が高く、したがってこの例よりもはるかに複雑です。

リエントラントであることはスレッドセーフであることと同じ *ではない* こ
とには注意が必要です。たとえば "redirect_stdout()" は、 "sys.stdout"
を異なるストリームに束縛することによりシステムの状態に対してグローバル
な変更を行うことから、明らかにスレッドセーフではありません。


再利用可能なコンテキストマネージャ
----------------------------------

単回使用のコンテキストマネージャとリエントラントなコンテキストマネージ
ャのいずれとも異なるタイプに "再利用可能" なコンテキストマネージャがあ
ります (あるいは、より明確には、"再利用可能だがリエントラントでない"
コンテキストマネージャです。 リエントラントなコンテキストマネージャも
また再利用可能だからです)。 再利用可能なコンテキストマネージャは複数回
利用をサポートしますが、同じコンテキストマネージャのインスタンスが外側
の with 文ですでに使用中の場合には失敗します (もしくは正しく動作しませ
ん)。

"threading.Lock" は再利用可能だがリエントラントでないコンテキストマネ
ージャの例です (リエントラントなロックのためには "threading.RLock" を
代わりに使う必要があります)。

再利用可能だがリエントラントでないコンテキストマネージャのもう一つの例
は "ExitStack" です。 これを使うどの with 文を抜けるときにも、現在登録
されている *すべての* コールバックを呼び出します。 コールバックをどこ
で登録したかは関係ありません。

   >>> from contextlib import ExitStack
   >>> stack = ExitStack()
   >>> with stack:
   ...     stack.callback(print, "Callback: from first context")
   ...     print("Leaving first context")
   ...
   Leaving first context
   Callback: from first context
   >>> with stack:
   ...     stack.callback(print, "Callback: from second context")
   ...     print("Leaving second context")
   ...
   Leaving second context
   Callback: from second context
   >>> with stack:
   ...     stack.callback(print, "Callback: from outer context")
   ...     with stack:
   ...         stack.callback(print, "Callback: from inner context")
   ...         print("Leaving inner context")
   ...     print("Leaving outer context")
   ...
   Leaving inner context
   Callback: from inner context
   Callback: from outer context
   Leaving outer context

例における出力が示すように、ひとつのスタックオブジェクトを複数の with
文で再利用しても正しく動作します。しかし入れ子にして使った場合は、一番
内側の with 文を抜ける際にスタックが空になります。これは望ましい動作と
は思えません。

ひとつの "ExitStack" インスタンスを再利用する代わりに複数のインスタン
スを使うことにより、この問題は回避することができます:

   >>> from contextlib import ExitStack
   >>> with ExitStack() as outer_stack:
   ...     outer_stack.callback(print, "Callback: from outer context")
   ...     with ExitStack() as inner_stack:
   ...         inner_stack.callback(print, "Callback: from inner context")
   ...         print("Leaving inner context")
   ...     print("Leaving outer context")
   ...
   Leaving inner context
   Callback: from inner context
   Leaving outer context
   Callback: from outer context
