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

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

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

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


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

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

class contextlib.AbstractContextManager

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

   バージョン 3.6 で追加.

class contextlib.AbstractAsyncContextManager

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

   バージョン 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"

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

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

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

@contextlib.asynccontextmanager

   Similar to "contextmanager()", but creates an asynchronous context
   manager.

   This function is a *decorator* that can be used to define a factory
   function for "async with" statement asynchronous context managers,
   without needing to create a class or separate "__aenter__()" and
   "__aexit__()" methods. It must be applied to an *asynchronous
   generator* function.

   簡単な例:

      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 ...')

   バージョン 3.7 で追加.

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('http://www.python.org')) as page:
          for line in page:
              print(line)

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

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

   An example using *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

   バージョン 3.7 で追加.

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

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

   バージョン 3.4 で追加.

contextlib.redirect_stdout(new_target)

   Context manager for temporarily redirecting "sys.stdout" to another
   file or file-like object.

   This tool adds flexibility to existing functions or classes whose
   output is hardwired to 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:

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

   To send the output of "help()" to a file on disk, redirect the
   output to a regular file:

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

   To send the output of "help()" to *sys.stderr*:

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

   Note that the global side effect on "sys.stdout" means that this
   context manager is not suitable for use in library code and most
   threaded applications. It also has no effect on the output of
   subprocesses. However, it is still a useful approach for many
   utility scripts.

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

   バージョン 3.4 で追加.

contextlib.redirect_stderr(new_target)

   Similar to "redirect_stdout()" but redirecting "sys.stderr" to
   another file or file-like object.

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

   バージョン 3.5 で追加.

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" 文を関数内で利用するべきです。

   バージョン 3.2 で追加.

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

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

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

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

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

   バージョン 3.3 で追加.

   enter_context(cm)

      新しいコンテキストマネージャーに enter し、その "__exit__()"
      method をコールバックスタックに追加します。渡されたコンテキスト
      マネージャーの "__enter__()" メソッドの戻り値を返します。

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

   push(exit)

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

      このメソッドは "__enter__" を **呼び出さない** ので、コンテキス
      トマネージャーを実装するときに、 "__enter__()" の実装の一部を自
      身の "__exit__()" メソッドでカバーするために利用できます。

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

      それらのコールバック関数も、コンテキストマネージャーの
      "__exit__()" と同じく、 true 値を返すことで例外を抑制することが
      できます。

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

   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

   An asynchronous context manager, similar to "ExitStack", that
   supports combining both synchronous and asynchronous context
   managers, as well as having coroutines for cleanup logic.

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

   enter_async_context(cm)

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

   push_async_exit(exit)

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

   push_async_callback(callback, *args, **kwds)

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

   aclose()

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

   Continuing the example for "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.

   バージョン 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__" 実装内のクリーンアップ
----------------------------------

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

これにより、別のフラグ変数を使う代わりに、必要なクリーンアップ処理を手
前に明示しておくことができます。

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

   from contextlib import ExitStack

   class Callback(ExitStack):
       def __init__(self, callback, *args, **kwds):
           super(Callback, self).__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()

コンテキストマネージャーを関数デコレーターとして使う場合、
"__enter__()" メソッドの戻り値にアクセスする手段がないという制限がある
ことに注意してください。もしその値が必要であれば、明示的な "with" 文を
使う必要があります。

参考:

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


Single use, reusable and reentrant context managers
===================================================

Most context managers are written in a way that means they can only be
used effectively in a "with" statement once. These single use context
managers must be created afresh each time they're used - attempting to
use them a second time will trigger an exception or otherwise not work
correctly.

This common limitation means that it is generally advisable to create
context managers directly in the header of the "with" statement where
they are used (as shown in all of the usage examples above).

Files are an example of effectively single use context managers, since
the first "with" statement will close the file, preventing any further
IO operations using that file object.

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


Reentrant context managers
--------------------------

More sophisticated context managers may be "reentrant". These context
managers can not only be used in multiple "with" statements, but may
also be used *inside* a "with" statement that is already using the
same context manager.

"threading.RLock" is an example of a reentrant context manager, as are
"suppress()" and "redirect_stdout()". Here's a very simple example of
reentrant use:

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

Real world examples of reentrancy are more likely to involve multiple
functions calling each other and hence be far more complicated than
this example.

Note also that being reentrant is *not* the same thing as being thread
safe. "redirect_stdout()", for example, is definitely not thread safe,
as it makes a global modification to the system state by binding
"sys.stdout" to a different stream.


Reusable context managers
-------------------------

Distinct from both single use and reentrant context managers are
"reusable" context managers (or, to be completely explicit, "reusable,
but not reentrant" context managers, since reentrant context managers
are also reusable). These context managers support being used multiple
times, but will fail (or otherwise not work correctly) if the specific
context manager instance has already been used in a containing with
statement.

"threading.Lock" is an example of a reusable, but not reentrant,
context manager (for a reentrant lock, it is necessary to use
"threading.RLock" instead).

Another example of a reusable, but not reentrant, context manager is
"ExitStack", as it invokes *all* currently registered callbacks when
leaving any with statement, regardless of where those callbacks were
added:

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

As the output from the example shows, reusing a single stack object
across multiple with statements works correctly, but attempting to
nest them will cause the stack to be cleared at the end of the
innermost with statement, which is unlikely to be desirable behaviour.

Using separate "ExitStack" instances instead of reusing a single
instance avoids that problem:

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