contextlib
--- with
文コンテキスト用ユーティリティ¶
ソースコード: Lib/contextlib.py
このモジュールは with
文に関わる一般的なタスクのためのユーティリティを提供します。詳しい情報は、 コンテキストマネージャ型 と with文とコンテキストマネージャ を参照してください。
ユーティリティ¶
以下の関数とクラスを提供しています:
-
class
contextlib.
AbstractContextManager
¶ object.__enter__()
とobject.__exit__()
の2つのメソッドを実装した抽象基底クラス (abstract base class) です。object.__enter__()
はself
を返すデフォルトの実装が提供されるいっぽう、object.__exit__()
はデフォルトでNone
を返す抽象メソッドです。 コンテキストマネージャ型 の定義も参照してください。バージョン 3.6 で追加.
-
class
contextlib.
AbstractAsyncContextManager
¶ object.__aenter__()
とobject.__aexit__()
の2つのメソッドを実装するクラスのための抽象基底クラス (abstract base class) です。object.__aenter__()
はself
を返すデフォルト実装が提供されるいっぽう、object.__aexit__()
はデフォルトでNone
を返す抽象メソッドです。 非同期コンテキストマネージャ (Asynchronous Context Manager) の定義も参照してください。バージョン 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 ...')
バージョン 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('https://www.python.org')) as page: for line in page: print(line)
page
を明示的に close する必要は無く、エラーが発生した場合でも、with
ブロックを出るときにpage.close()
が呼ばれます。
-
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
バージョン 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
を一時的に別のファイルまたは 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
のシステム全体にわたる副作用により、このコンテキストマネージャはライブラリコードやマルチスレッドアプリケーションでの使用には適していません。また、サブプロセスの出力に対しても効果がありません。そのような制限はありますが、それでも多くのユーティリティスクリプトに対して有用なアプローチです。このコンテキストマネージャは 再入可能(リエントラント) です。
バージョン 3.4 で追加.
-
contextlib.
redirect_stderr
(new_target)¶ redirect_stdout()
と同じですが、標準エラー出力 (sys.stderr
) を別のファイルや file-like オブジェクトにリダイレクトします。このコンテキストマネージャは 再入可能(リエントラント) です。
バージョン 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
The
__enter__()
method returns theExitStack
instance, and performs no additional operations.各インスタンスは登録されたコールバックのスタックを管理し、インスタンスが (明示的に、あるいは
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
¶ ExitStack
に似た 非同期コンテキストマネージャ です。スタック上で同期と非同期の両方のコンテキストマネージャの組み合わせをサポートします。また、後処理のためのコルーチンも持っています。close()
メソッドは実装されていません。代わりにaclose()
を使ってください。-
coroutine
enter_async_context
(cm)¶ enter_context()
と同様のメソッドですが、非同期コンテキストマネージャを受け取ります。
-
push_async_exit
(exit)¶ push()
と同様のメソッドですが、非同期コンテキストマネージャかコルーチン関数を受け取ります。
-
push_async_callback
(callback, /, *args, **kwds)¶ callback()
と同様のメソッドですが、コルーチン関数を受け取ります。
-
coroutine
aclose
()¶ close()
と同様のメソッドですが、待ち受け可能オブジェクト (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.
バージョン 3.7 で追加.
-
coroutine
例とレシピ¶
このセクションでは、 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().__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
文を使う必要があります。
単回使用、再利用可能、およびリエントラントなコンテキストマネージャ¶
ほとんどのコンテキストマネージャは、 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
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
リエントラントな性質の実例はお互いを呼び出しあう複数の関数を含んでいる可能性が高く、したがってこの例よりもはるかに複雑です。
リエントラントであることはスレッドセーフであることと同じ ではない ことには注意が必要です。たとえば 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