# `contextlib` --- `with` 文コンテキスト用ユーティリティ

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

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

## ユーティリティ

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

class contextlib.AbstractContextManager

  `object.__enter__()` と `object.__exit__()` の2つのメソッドを実装した抽象基底クラス (abstract base class) です。 `object.__enter__()` は `self` を返すデフォルトの実装が提供されるいっぽう、 `object.__exit__()` はデフォルトで `None` を返す抽象メソッドです。 コンテキストマネージャ型 の定義も参照してください。

  Added in version 3.6.

class contextlib.AbstractAsyncContextManager

  `object.__aenter__()` と `object.__aexit__()` の2つのメソッドを実装するクラスのための抽象基底クラス (abstract base class) です。 `object.__aenter__()` は `self` を返すデフォルト実装が提供されるいっぽう、 `object.__aexit__()` はデフォルトで `None` を返す抽象メソッドです。 非同期コンテキストマネージャ (Asynchronous Context Manager) の定義も参照してください。

  Added in version 3.7.

@contextlib.contextmanager

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

  多くのオブジェクトが with 文の仕様を固有にサポートしていますが、コンテキストマネージャの権限に属さず、 `close()` メソッドを実装していないために `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)

  enter_result を `__enter__` メソッドが返すだけで、その他は何もしないコンテキストマネージャを返します。たとえば以下のように、ある機能を持った別のコンテキストマネージャに対する、選択可能な代役として使われることが意図されています:

      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) に固定されている既存の関数やクラスに出力先の柔軟性を追加します。

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

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

  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():
              # 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.

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

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

## 例とレシピ

このセクションでは、 `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__` メソッドからの例外をキャッチする

稀に、 `__enter__` メソッドからの例外を、 `with` 文の body やコンテキストマネージャーの `__exit__` メソッドからの例外は間違えて捕まえないように、 catch したい場合があります。 `ExitStack` を使って、コンテキストマネージャープロトコル内のステップを分離することができます:

    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