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

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

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

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


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

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

class contextlib.AbstractContextManager

   An *abstract base class* for classes that implement "__enter__()"
   and "__exit__()". A default implementation for "__enter__()" is
   provided which returns "self" while "__exit__()" is an abstract
   method which by default returns "None". See also the definition of
   コンテキストマネージャ型.

   Added in version 3.6.

class contextlib.AbstractAsyncContextManager

   An *abstract base class* for classes that implement "__aenter__()"
   and "__aexit__()". A default implementation for "__aenter__()" is
   provided which returns "self" while "__aexit__()" is an abstract
   method which by default returns "None". See also the definition of
   非同期コンテキストマネージャ (Asynchronous Context Manager).

   Added in version 3.7.

@contextlib.contextmanager

   この関数は "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".

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

      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" 文の直後の文から実行を再開します。

   "contextmanager()" は "ContextDecorator" を使っているので、
   "contextmanager()" で作ったコンテキストマネージャは "with" 文だけで
   なくデコレータとしても利用できます。デコレーターとして利用された場
   合、新しい generator インスタンスが関数呼び出しのたびに暗黙に生成さ
   れます (このことによって、 "contextmanager()" によって作られたなに
   がしか「単発」コンテキストマネージャを、コンテキストマネージャがデ
   コレータとして使われるためには多重に呼び出されることをサポートする
   必要がある、という要件に合致させることが出来ます。)

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

@contextlib.asynccontextmanager

   "contextmanager()" と似ていますが、 非同期コンテキストマネージャ
   (asynchronous context manager) を生成します。

   この関数は "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():
          # ... async code ...

   デコレータとして使われた場合は、各関数呼び出しに対して暗黙のうちに
   新しいジェネレータインスタンスが生成されます。これにより、
   "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()" が呼ばれます。

   注釈:

     Most types managing resources support the *context manager*
     protocol, which closes *thing* on leaving the "with" statement.
     As such, "closing()" is most useful for third party types that
     don't support context managers. This example is purely for
     illustration purposes, as "urlopen()" would normally be used in a
     context manager.

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)

   Return a context manager that returns *enter_result* from
   "__enter__()", but otherwise does nothing. It is intended to be
   used as a stand-in for an optional context manager, for example:

      def myfunction(arg, ignore_exceptions=False):
          if ignore_exceptions:
              # Use suppress to ignore all exceptions.
              cm = contextlib.suppress(Exception)
          else:
              # Do not ignore any exceptions, cm has no effect.
              cm = contextlib.nullcontext()
          with cm:
              # Do something

   *enter_result* を使った例です:

      def process_file(file_or_path):
          if isinstance(file_or_path, str):
              # If string, open file
              cm = open(file_or_path)
          else:
              # Caller is responsible for closing file
              cm = nullcontext(file_or_path)

          with cm as file:
              # Perform processing on the file

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

      async def send_http(session=None):
          if not session:
              # If no http session, create it with aiohttp
              cm = aiohttp.ClientSession()
          else:
              # Caller is responsible for closing the session
              cm = nullcontext(session)

          async with cm as session:
              # Send http requests with session

   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

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

   If the code within the "with" block raises a "BaseExceptionGroup",
   suppressed exceptions are removed from the group.  Any exceptions
   of the group which are not suppressed are re-raised in a new group
   which is created using the original group's "derive()" method.

   Added in version 3.4.

   バージョン 3.12 で変更: "suppress" now supports suppressing
   exceptions raised as part of a "BaseExceptionGroup".

contextlib.redirect_stdout(new_target)

   "sys.stdout" を一時的に別のファイルまたは file-like オブジェクトに
   リダイレクトするコンテキストマネージャです。

   このツールは、出力先が標準出力 (stdout) に固定されている既存の関数
   やクラスに出力先の柔軟性を追加します。

   For example, the output of "help()" normally is sent to
   *sys.stdout*. You can capture that output in a string by
   redirecting the output to an "io.StringIO" object. The replacement
   stream is returned from the "__enter__()" method and so is
   available as the target of the "with" statement:

      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-like オブジェクトにリダイレクトします。

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

   Added in version 3.5.

contextlib.chdir(path)

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

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

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

   Added in version 3.11.

class contextlib.ContextDecorator

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

   Context managers inheriting from "ContextDecorator" have to
   implement "__enter__()" and "__exit__()" as normal. "__exit__"
   retains its optional exception handling even when used as a
   decorator.

   "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():
              # Do stuff

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

      @cm()
      def f():
          # Do stuff

   デコレーターを使うと、"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" 文を関数内で利用するべきです。

   Added in version 3.2.

class contextlib.AsyncContextDecorator

   "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.

class contextlib.ExitStack

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

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

      with ExitStack() as stack:
          files = [stack.enter_context(open(fname)) for fname in filenames]
          # All opened files will automatically be closed at the end of
          # the with statement, even if attempts to open files later
          # in the list raise an exception

   The "__enter__()" method returns the "ExitStack" instance, and
   performs no additional operations.

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

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

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

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

   Added in version 3.3.

   enter_context(cm)

      Enters a new context manager and adds its "__exit__()" method to
      the callback stack. The return value is the result of the
      context manager's own "__enter__()" method.

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

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

   push(exit)

      Adds a context manager's "__exit__()" method to the callback
      stack.

      As "__enter__" is *not* invoked, this method can be used to
      cover part of an "__enter__()" implementation with a context
      manager's own "__exit__()" method.

      If passed an object that is not a context manager, this method
      assumes it is a callback with the same signature as a context
      manager's "__exit__()" method and adds it directly to the
      callback stack.

      By returning true values, these callbacks can suppress
      exceptions the same way context manager "__exit__()" methods
      can.

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

   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]
             # Hold onto the close method, but don't call it yet.
             close_files = stack.pop_all().close
             # If opening any file fails, all previously opened files will be
             # closed automatically. If all files are opened successfully,
             # they will remain open even after the with statement ends.
             # close_files() can then be invoked explicitly to close them all.

   close()

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

class contextlib.AsyncExitStack

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

   The "close()" method is not implemented; "aclose()" must be used
   instead.

   async enter_async_context(cm)

      Similar to "ExitStack.enter_context()" but expects an
      asynchronous context manager.

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

   push_async_exit(exit)

      Similar to "ExitStack.push()" but expects either an asynchronous
      context manager or a coroutine function.

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

      Similar to "ExitStack.callback()" but expects a coroutine
      function.

   async aclose()

      Similar to "ExitStack.close()" but properly handles awaitables.

   "asynccontextmanager()" の使用例の続きです:

      async with AsyncExitStack() as stack:
          connections = [await stack.enter_async_context(get_connection())
              for i in range(5)]
          # All opened connections will automatically be released at the end of
          # the async with statement, even if attempts to open a connection
          # later in the list raise an exception.

   Added in version 3.7.


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

This section describes some examples and recipes for making effective
use of the tools provided by "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)
       # Perform operations that use the acquired resources

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


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

It is occasionally desirable to catch exceptions from an "__enter__()"
method implementation, *without* inadvertently catching exceptions
from the "with" statement body or the context manager's "__exit__()"
method. By using "ExitStack" the steps in the context management
protocol can be separated slightly in order to allow this:

   stack = ExitStack()
   try:
       x = stack.enter_context(cm)
   except Exception:
       # handle __enter__ exception
   else:
       with stack:
           # Handle normal case

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


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

As noted in the documentation of "ExitStack.push()", this method can
be useful in cleaning up an already allocated resource if later steps
in the "__enter__()" implementation fail.

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

   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
               # The validation check passed and didn't raise an exception
               # Accordingly, we want to keep the resource, and pass it
               # back to our caller
               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):
           # We don't need to duplicate any of our resource release logic
           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()

This allows the intended cleanup behaviour to be made explicit up
front, rather than requiring a separate flag variable.

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

   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()

Note that there is one additional limitation when using context
managers as function decorators: there's no way to access the return
value of "__enter__()". If that value is needed, then it is still
necessary to use an explicit "with" statement.

参考:

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


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

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

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

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

"contextmanager()" により生成されたコンテキストマネージャも単回使用の
コンテキスト  マネージャです。二回目に使おうとした場合、内部にあるジェ
ネレータが値の生成に失敗したと訴えるでしょう:

   >>> 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" です。これは現在登録されている *全ての* コールバック
関数を、どこで登録されたかにかかわらず、呼び出します:

   >>> 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
