contextlib — with-statementコンテキストのユーティリティ—Pythonドキュメント

提供:Dev Guides
< PythonPython/docs/3.8/library/contextlib
移動先:案内検索

contextlib — withステートメントコンテキストのユーティリティ

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



このモジュールは、 with ステートメントに関連する一般的なタスクのユーティリティを提供します。 詳細については、コンテキストマネージャータイプおよびステートメントコンテキストマネージャーありも参照してください。

ユーティリティ

提供される関数とクラス:

class contextlib.AbstractContextManager

object .__ enter __()および object .__ exit __()を実装するクラスの抽象基本クラスobject .__ enter __()のデフォルトの実装は、selfを返しますが、 object .__ exit __()は、デフォルトでNoneを返す抽象メソッドです。 ]。 コンテキストマネージャータイプの定義も参照してください。

バージョン3.6の新機能。

class contextlib.AbstractAsyncContextManager

object .__ aenter __()および object .__ aexit __()を実装するクラスの抽象基本クラスobject .__ aenter __()のデフォルトの実装が提供され、selfが返されます。一方、 object .__ aexit __()は、デフォルトでNoneを返す抽象メソッドです。 ]。 非同期コンテキストマネージャーの定義も参照してください。

バージョン3.7の新機能。

@contextlib.contextmanager

この関数はデコレータであり、クラスを作成したり、__enter__()__exit__()メソッド。

多くのオブジェクトはwithステートメントでの使用をネイティブにサポートしていますが、それ自体がコンテキストマネージャーではなく、contextlib.closingで使用するclose()メソッドを実装していないリソースを管理する必要がある場合があります。 ]

正しいリソース管理を確実にするための抽象的な例は次のとおりです。

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

デコレーションされる関数は、呼び出されたときにジェネレーター-イテレーターを返す必要があります。 このイテレータは、 with ステートメントのas句のターゲットにバインドされる値を1つだけ生成する必要があります(存在する場合)。

ジェネレーターが降伏する時点で、 with ステートメントにネストされたブロックが実行されます。 ブロックが終了すると、ジェネレータが再開されます。 未処理の例外がブロックで発生した場合、yieldが発生したポイントでジェネレーター内で再発生します。 したがって、 tryexceptfinally ステートメントを使用して、エラー(存在する場合)をトラップするか、何らかのクリーンアップが行われるようにすることができます。 例外をログに記録するため、または何らかのアクションを実行するために(完全に抑制するのではなく)例外がトラップされた場合、ジェネレーターはその例外を再発生させる必要があります。 それ以外の場合、ジェネレータコンテキストマネージャはwithステートメントに例外が処理されたことを示し、実行はwithステートメントの直後のステートメントから再開されます。

contextmanager()ContextDecorator を使用するため、作成するコンテキストマネージャーは with ステートメントと同様にデコレーターとして使用できます。 デコレータとして使用すると、関数呼び出しごとに新しいジェネレータインスタンスが暗黙的に作成されます(これにより、 contextmanager()によって作成された「ワンショット」コンテキストマネージャが、コンテキストマネージャが複数の呼び出しをサポートするという要件を満たすことができます。デコレータとして使用するため)。

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

@contextlib.asynccontextmanager

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

この関数はデコレータであり、クラスを作成したり、__aenter__()と[ X209X] メソッド。 非同期ジェネレーター機能に適用する必要があります。

簡単な例:

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 を閉じるコンテキストマネージャーを返します。 これは基本的に次と同等です。

from contextlib import contextmanager

@contextmanager
def closing(thing):
    try:
        yield thing
    finally:
        thing.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を明示的に閉じる必要はありません。 エラーが発生した場合でも、 with ブロックが終了するとpage.close()が呼び出されます。

contextlib.nullcontext(enter_result=None)

__enter__から enter_result を返すコンテキストマネージャーを返しますが、それ以外は何もしません。 これは、オプションのコンテキストマネージャーの代用として使用することを目的としています。次に例を示します。

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

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

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

このツールは、出力がstdoutにハードワイヤードされている既存の関数またはクラスに柔軟性を追加します。

たとえば、 help()の出力は通常、 sys.stdout に送信されます。 出力を io.StringIO オブジェクトにリダイレクトすることにより、その出力を文字列に取り込むことができます。

f = io.StringIO()
with redirect_stdout(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 のグローバルな副作用は、このコンテキストマネージャーがライブラリコードやほとんどのスレッド化されたアプリケーションでの使用に適していないことを意味することに注意してください。 また、サブプロセスの出力にも影響を与えません。 ただし、それでも多くのユーティリティスクリプトにとって有用なアプローチです。

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

バージョン3.4の新機能。

contextlib.redirect_stderr(new_target)

redirect_stdout()に似ていますが、 sys.stderr を別のファイルまたはファイルのようなオブジェクトにリダイレクトします。

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

バージョン3.5の新機能。

class contextlib.ContextDecorator

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

ContextDecoratorを継承するコンテキストマネージャーは、通常どおり__enter____exit__を実装する必要があります。 __exit__は、デコレータとして使用されている場合でも、オプションの例外処理を保持します。

ContextDecoratorcontextmanager()によって使用されるため、この機能は自動的に取得されます。

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が関数の一部ではなく、関数全体に適用されることを明確にしています(そして、インデントレベルを保存するのも良いことです)。

すでに基本クラスを持っている既存のコンテキストマネージャーは、ContextDecoratorをミックスインクラスとして使用することで拡張できます。

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 ステートメントの最後で明示的または暗黙的に)。 コンテキストスタックインスタンスがガベージコレクションされる場合、コールバックは暗黙的に呼び出されないことに注意してください。

このスタックモデルは、__init__メソッドでリソースを取得するコンテキストマネージャー(ファイルオブジェクトなど)を正しく処理できるようにするために使用されます。

登録されたコールバックは登録の逆の順序で呼び出されるため、これは、登録されたコールバックのセットで複数のネストされた with ステートメントが使用されたかのように動作します。 これは例外処理にも拡張されます。内部コールバックが例外を抑制または置換する場合、外部コールバックにはその更新された状態に基づいて引数が渡されます。

これは比較的低レベルのAPIであり、出口コールバックのスタックを正しく巻き戻す詳細を処理します。 これは、アプリケーション固有の方法で出口スタックを操作する高レベルのコンテキストマネージャーに適した基盤を提供します。

バージョン3.3の新機能。

enter_context(cm)

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

これらのコンテキストマネージャは、 with ステートメントの一部として直接使用される場合と同じように、例外を抑制することができます。

push(exit)

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

__enter__ではなく呼び出されるため、このメソッドを使用して、__enter__()実装の一部をコンテキストマネージャー独自の__exit__()メソッドでカバーできます。

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

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

渡されたオブジェクトは関数から返され、このメソッドを関数デコレータとして使用できるようになります。

callback(callback, *args, **kwds)

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

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

渡されたコールバックは関数から返され、このメソッドを関数デコレータとして使用できるようになります。

pop_all()

コールバックスタックを新しい ExitStack インスタンスに転送し、それを返します。 この操作ではコールバックは呼び出されません。代わりに、新しいスタックが閉じられたときに呼び出されるようになりました( with ステートメントの最後で明示的または暗黙的に)。

たとえば、ファイルのグループは、次のように「オールオアナッシング」操作として開くことができます。

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

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

class contextlib.AsyncExitStack

非同期コンテキストマネージャーは、 ExitStack と同様で、同期コンテキストマネージャーと非同期コンテキストマネージャーの両方の組み合わせをサポートし、クリーンアップロジックのコルーチンを備えています。

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

enter_async_context(cm)

enter_context()に似ていますが、非同期コンテキストマネージャーが必要です。

push_async_exit(exit)

push()に似ていますが、非同期コンテキストマネージャーまたはコルーチン関数のいずれかが必要です。

push_async_callback(callback, *args, **kwds)

callback()に似ていますが、コルーチン関数が必要です。

aclose()

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

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 ステートメント本体またはコンテキストマネージャーの__exit__メソッドからの例外を誤ってキャッチします。 。 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およびフラグ変数の使用を置き換える

時々見られるパターンは、finally句の本体を実行する必要があるかどうかを示すフラグ変数を持つtry-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 から継承すると、単一の定義で両方の機能が提供されます。

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 ステートメントで一度だけ効果的に使用できるように記述されています。 これらの使い捨てコンテキストマネージャーは、使用するたびに新たに作成する必要があります。2回目に使用しようとすると、例外がトリガーされるか、正しく機能しません。

この一般的な制限は、コンテキストマネージャーを使用する with ステートメントのヘッダーに直接コンテキストマネージャーを作成することをお勧めします(上記のすべての使用例に示されているように)。

最初の with ステートメントはファイルを閉じ、そのファイルオブジェクトを使用したそれ以上のIO操作を防ぐため、ファイルは効果的に使い捨てのコンテキストマネージャーの例です。

contextmanager()を使用して作成されたコンテキストマネージャーも使い捨てのコンテキストマネージャーであり、2回目に使用しようとすると、基になるジェネレーターが生成されないことについて文句を言います。

>>> 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()と同様に、再入可能なコンテキストマネージャーの例です。 リエントラントの使用の非常に簡単な例を次に示します。

>>> 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 を別のストリームにバインドすることでシステム状態をグローバルに変更するため、スレッドセーフではありません。


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

使い捨てコンテキストマネージャーと再入可能コンテキストマネージャーの両方とは異なり、「再利用可能な」コンテキストマネージャーです(または、完全に明示的に言うと、再入可能コンテキストマネージャーも再利用可能であるため、「再利用可能ですが、再入可能ではありません」コンテキストマネージャーです)。 これらのコンテキストマネージャーは複数回の使用をサポートしていますが、特定のコンテキストマネージャーインスタンスがcontaining withステートメントですでに使用されている場合は失敗します(または正しく機能しません)。

threading.Lock は、再利用可能ですが、再入可能ではないコンテキストマネージャーの例です(再入可能ロックの場合は、代わりに threading.RLock を使用する必要があります)。

再利用可能であるが再入可能ではないコンテキストマネージャーの別の例は、 ExitStack です。これは、コールバックが追加された場所に関係なく、withステートメントを残すときに all 現在登録されているコールバックを呼び出すためです。

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