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

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


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

ユーティリティ

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

classcontextlib.AbstractContextManager

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

バージョン 3.6 で追加.

classcontextlib.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 の利用もできないリソースを管理する必要があることがあります。

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

fromcontextlibimportcontextmanager@contextmanagerdefmanaged_resource(*args,**kwds):# Code to acquire resource, e.g.:resource=acquire_resource(*args,**kwds)try:yieldresourcefinally:# Code to release resource, e.g.:release_resource(resource)

このとき、関数は次のように使うことができます:

>>>withmanaged_resource(timeout=3600)asresource:...# 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) を生成します。

この関数はasyncwith 文のための非同期コンテキストマネージャのファクトリ関数を定義するために利用できるデコレータ (decorator) です。新しいクラスや__aenter__()__aexit__() メソッドを個別に定義する必要はありません。このデコレータは非同期ジェネレータ (asynchronous generator) 関数に適用しなければなりません。

簡単な例:

fromcontextlibimportasynccontextmanager@asynccontextmanagerasyncdefget_connection():conn=awaitacquire_db_connection()try:yieldconnfinally:awaitrelease_db_connection(conn)asyncdefget_all_users():asyncwithget_connection()asconn:returnconn.query('SELECT ...')

バージョン 3.7 で追加.

asynccontextmanager() とともに定義されたコンテキストマネージャは、デコレータとして使うこともasyncwith 文と組み合わせて使うこともできます:

importtimefromcontextlibimportasynccontextmanager@asynccontextmanagerasyncdeftimeit():now=time.monotonic()try:yieldfinally:print(f'it took{time.monotonic()-now}s to run')@timeit()asyncdefmain():# ... async code ...

デコレータとして使われた場合は、各関数呼び出しに対して暗黙のうちに新しいジェネレータインスタンスが生成されます。これにより、asynccontextmanager() で生成された単回使用のコンテキストマネージャが複数回呼び出し可能となり、コンテキストマネージャをデコレータとして使うことができるための要件を満たすようにすることができます。

バージョン 3.10 で変更:asynccontextmanager() により生成された非同期コンテキストマネージャをデコレータとして使うことができるようになりました。

contextlib.closing(thing)

ブロックの完了時にthing を close するコンテキストマネージャを返します。これは基本的に以下と等価です:

fromcontextlibimportcontextmanager@contextmanagerdefclosing(thing):try:yieldthingfinally:thing.close()

そして、明示的にpage を close する必要なしに、次のように書くことができます:

fromcontextlibimportclosingfromurllib.requestimporturlopenwithclosing(urlopen('https://www.python.org'))aspage:forlineinpage:print(line)

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

contextlib.aclosing(thing)

ブロックの完了時にthingaclose() メソッドを呼び出すような非同期コンテキストマネージャを返します。これは基本的に以下と等価です:

fromcontextlibimportasynccontextmanager@asynccontextmanagerasyncdefaclosing(thing):try:yieldthingfinally:awaitthing.aclose()

重要なことは、たとえば次の例のようにbreak や例外によって早期にブロックが終了した場合に、aclosing() は非同期ジェネレータの決定論的なクリーンアップをサポートすることです:

fromcontextlibimportaclosingasyncwithaclosing(my_generator())asvalues:asyncforvalueinvalues:ifvalue==42:break

このパターンは、ジェネレータの非同期な終了のコードがイテレーション処理と同じコンテキストの中で実行されることを保証します (すなわち例外とコンテキスト変数は期待通りに動作し、またジェネレータが依存するタスクの寿命が尽きたあとに終了のコードが実行されることもありません)。

バージョン 3.10 で追加.

contextlib.nullcontext(enter_result=None)

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

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

enter_result を使った例です:

defprocess_file(file_or_path):ifisinstance(file_or_path,str):# If string, open filecm=open(file_or_path)else:# Caller is responsible for closing filecm=nullcontext(file_or_path)withcmasfile:# Perform processing on the file

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

asyncdefsend_http(session=None):ifnotsession:# If no http session, create it with aiohttpcm=aiohttp.ClientSession()else:# Caller is responsible for closing the sessioncm=nullcontext(session)asyncwithcmassession:# Send http requests with session

バージョン 3.7 で追加.

バージョン 3.10 で変更:非同期コンテキストマネージャ (asynchronous context manager) のサポートが追加されました。

contextlib.suppress(*exceptions)

with 文の内部で指定された例外の発生を抑えるコンテキストマネージャを返します。with 文の後に続く最初の文から処理が再開されます。

ほかの完全に例外を抑制するメカニズム同様、このコンテキストマネージャは、黙ってプログラム実行を続けることが正しいことであるとわかっている、非常に限定的なエラーをカバーする以上の使い方はしてはいけません。

例えば:

fromcontextlibimportsuppresswithsuppress(FileNotFoundError):os.remove('somefile.tmp')withsuppress(FileNotFoundError):os.remove('someotherfile.tmp')

これは以下と等価です:

try:os.remove('somefile.tmp')exceptFileNotFoundError:passtry:os.remove('someotherfile.tmp')exceptFileNotFoundError:pass

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

バージョン 3.4 で追加.

contextlib.redirect_stdout(new_target)

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

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

たとえば、help() の出力は通常標準出力 (sys.stdout) に送られます。出力される文字列をio.StringIO オブジェクトにリダイレクトすることによって捕捉することができます。代替の出力ストリームは__enter__ メソッドによって返されるので、with 文のターゲットとして利用可能です:

withredirect_stdout(io.StringIO())asf:help(pow)s=f.getvalue()

help() の出力をディスク上のファイルに送るためには、出力を通常のファイルにリダイレクトします:

withopen('help.txt','w')asf:withredirect_stdout(f):help(pow)

help() の出力を標準エラー出力 (sys.stderr) に送るには以下のようにします:

withredirect_stdout(sys.stderr):help(pow)

sys.stdout のシステム全体にわたる副作用により、このコンテキストマネージャはライブラリコードやマルチスレッドアプリケーションでの使用には適していません。また、サブプロセスの出力に対しても効果がありません。そのような制限はありますが、それでも多くのユーティリティスクリプトに対して有用なアプローチです。

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

バージョン 3.4 で追加.

contextlib.redirect_stderr(new_target)

redirect_stdout() と同じですが、標準エラー出力 (sys.stderr) を別のファイルや file-like オブジェクトにリダイレクトします。

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

バージョン 3.5 で追加.

classcontextlib.ContextDecorator

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

ContextDecorator から継承したコンテキストマネージャは、通常のコンテキストマネージャーと同じく__enter__ および__exit__ を実装する必要があります。__exit__ はデコレータとして使用された場合でも例外をオプションの引数として受け取ります。

contextmanager()ContextDecorator を利用しているので、自動的にデコレーターとしても利用できるようになります。

ContextDecorator の例:

fromcontextlibimportContextDecoratorclassmycontext(ContextDecorator):def__enter__(self):print('Starting')returnselfdef__exit__(self,*exc):print('Finishing')returnFalse

このとき、クラスは次のように使うことができます:

>>>@mycontext()...deffunction():...print('The bit in the middle')...>>>function()StartingThe bit in the middleFinishing>>>withmycontext():...print('The bit in the middle')...StartingThe bit in the middleFinishing

これは次のような形のコードに対するシンタックスシュガーになります:

deff():withcm():# Do stuff

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

@cm()deff():# Do stuff

デコレーターを使うと、cm が関数の一部ではなく全体に適用されていることが明確になります (インデントレベルを1つ節約できるのもメリットです)。

すでに基底クラスを持っているコンテキストマネージャーも、ContextDecorator を mixin クラスとして利用することで拡張できます:

fromcontextlibimportContextDecoratorclassmycontext(ContextBaseClass,ContextDecorator):def__enter__(self):returnselfdef__exit__(self,*exc):returnFalse

注釈

デコレートされた関数が複数回呼び出せるように、内部のコンテキストマネージャーは複数のwith 文に対応する必要があります。そうでないなら、明示的なwith 文を関数内で利用するべきです。

バージョン 3.2 で追加.

classcontextlib.AsyncContextDecorator

ContextDecorator と同じですが、非同期関数専用のクラスです。

AsyncContextDecorator の使用例:

fromasyncioimportrunfromcontextlibimportAsyncContextDecoratorclassmycontext(AsyncContextDecorator):asyncdef__aenter__(self):print('Starting')returnselfasyncdef__aexit__(self,*exc):print('Finishing')returnFalse

このとき、クラスは次のように使うことができます:

>>>@mycontext()...asyncdeffunction():...print('The bit in the middle')...>>>run(function())StartingThe bit in the middleFinishing>>>asyncdeffunction():...asyncwithmycontext():...print('The bit in the middle')...>>>run(function())StartingThe bit in the middleFinishing

バージョン 3.10 で追加.

classcontextlib.ExitStack

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

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

withExitStack()asstack:files=[stack.enter_context(open(fname))forfnameinfilenames]# 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

__enter__() メソッドはExitStack インスタンスを返し、それ以外のいかなる処理も行いません。

各インスタンスは登録されたコールバックのスタックを管理し、インスタンスが (明示的に、あるいは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" に開く処理を次のように書けます:

withExitStack()asstack:files=[stack.enter_context(open(fname))forfnameinfilenames]# 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 に、例外が起こらなかった場合の引数が渡されます。

classcontextlib.AsyncExitStack

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

close() メソッドは実装されていません。代わりにaclose() を使ってください。

coroutineenter_async_context(cm)

enter_context() と同様のメソッドですが、非同期コンテキストマネージャを受け取ります。

push_async_exit(exit)

push() と同様のメソッドですが、非同期コンテキストマネージャかコルーチン関数を受け取ります。

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

callback() と同様のメソッドですが、コルーチン関数を受け取ります。

coroutineaclose()

close() と同様のメソッドですが、待ち受け可能オブジェクト (awaitables) を適切に処理します。

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

asyncwithAsyncExitStack()asstack:connections=[awaitstack.enter_async_context(get_connection())foriinrange(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 文で可変数個のコンテキストマネージャーや他のクリーンアップ関数をサポートすることです。ユーザーの入力 (指定された複数個のファイルを開く場合など) に応じて複数個のコンテキストマネージャーが必要となる場合や、いくつかのコンテキストマネージャーがオプションとなる場合に、可変数個のコンテキストマネージャーが必要になります:

withExitStack()asstack:forresourceinresources:stack.enter_context(resource)ifneed_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)exceptException:# handle __enter__ exceptionelse:withstack:# Handle normal case

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

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

ExitStack.push() のドキュメントで言及したとおり、このメソッドはすでに獲得したリソースを、__enter__() メソッドの残りのステップが失敗した時にクリーンアップするために利用することができます。

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

fromcontextlibimportcontextmanager,AbstractContextManager,ExitStackclassResourceManager(AbstractContextManager):def__init__(self,acquire_resource,release_resource,check_resource_ok=None):self.acquire_resource=acquire_resourceself.release_resource=release_resourceifcheck_resource_okisNone:defcheck_resource_ok(resource):returnTrueself.check_resource_ok=check_resource_ok@contextmanagerdef_cleanup_on_error(self):withExitStack()asstack: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 callerstack.pop_all()def__enter__(self):resource=self.acquire_resource()withself._cleanup_on_error():ifnotself.check_resource_ok(resource):msg="Failed validation for{!r}"raiseRuntimeError(msg.format(resource))returnresourcedef__exit__(self,*exc_details):# We don't need to duplicate any of our resource release logicself.release_resource()

try-finally + flag 変数パターンを置き換える

try-finally 文に、finally 句の内容を実行するかどうかを示すフラグ変数を組み合わせたパターンを目にすることがあるかもしれません。一番シンプルな (単にexcept 句を使うだけでは処理できない) ケースでは次のようなコードになります:

cleanup_needed=Truetry:result=perform_operation()ifresult:cleanup_needed=Falsefinally:ifcleanup_needed:cleanup_resources()

try 文を使ったコードでは、セットアップとクリーンアップのコードが任意の長さのコードで分離してしまうので、開発者やレビューアにとって問題になりえます。

ExitStack を使えば、代わりにwith 文の終わりに実行されるコールバックを登録し、後でそのコールバックをスキップするかどうかを決定できます:

fromcontextlibimportExitStackwithExitStack()asstack:stack.callback(cleanup_resources)result=perform_operation()ifresult:stack.pop_all()

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

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

fromcontextlibimportExitStackclassCallback(ExitStack):def__init__(self,callback,/,*args,**kwds):super().__init__()self.callback(callback,*args,**kwds)defcancel(self):self.pop_all()withCallback(cleanup_resources)ascb:result=perform_operation()ifresult:cb.cancel()

もしリソースのクリーンアップが単体の関数にまとまってない場合でも、ExitStack.callback() のデコレーター形式を利用してリソース開放処理を宣言することができます:

fromcontextlibimportExitStackwithExitStack()asstack:@stack.callbackdefcleanup_resources():...result=perform_operation()ifresult:stack.pop_all()

デコレータープロトコルの使用上、このように宣言されたコールバック関数は引数を取ることができません。その代わりに、リリースするリソースをクロージャー変数としてアクセスできる必要があります。

コンテキストマネージャーを関数デコレーターとして使う

ContextDecorator はコンテキストマネージャーを通常のwith 文に加えて関数デコレーターとしても利用できるようにします。

例えば、関数やまとまった文を、そこに入った時と出た時の時間をトラックするロガーでラップしたい場合があります。そのために関数デコレーターとコンテキストマネージャーを別々に書く代わりに、ContextDecorator を継承すると1つの定義で両方の機能を提供できます:

fromcontextlibimportContextDecoratorimportlogginglogging.basicConfig(level=logging.INFO)classtrack_entry_and_exit(ContextDecorator):def__init__(self,name):self.name=namedef__enter__(self):logging.info('Entering:%s',self.name)def__exit__(self,exc_type,exc,exc_tb):logging.info('Exiting:%s',self.name)

このクラスのインスタンスはコンテキストマネージャーとしても利用でき:

withtrack_entry_and_exit('widget loader'):print('Some time consuming activity goes here')load_widget()

また関数デコレーターとしても利用できます:

@track_entry_and_exit('widget loader')defactivity():print('Some time consuming activity goes here')load_widget()

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

参考

PEP 343 - "with" ステートメント

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

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

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

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

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

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

>>>fromcontextlibimportcontextmanager>>>@contextmanager...defsingleuse():...print("Before")...yield...print("After")...>>>cm=singleuse()>>>withcm:...pass...BeforeAfter>>>withcm:...pass...Traceback (most recent call last):...RuntimeError:generator didn't yield

リエントラントなコンテキストマネージャ

より洗練されたコンテキストマネージャには"リエントラント"なものがあります。そのようなコンテキストマネージャは、複数のwith 文で使えるだけでなく、同じコンテキストマネージャをすでに使っているwith 文の内部 でも使うことができます。

threading.RLock is an example of a reentrant context manager, as aresuppress() andredirect_stdout(). Here's a very simple example ofreentrant use:

>>>fromcontextlibimportredirect_stdout>>>fromioimportStringIO>>>stream=StringIO()>>>write_to_stream=redirect_stdout(stream)>>>withwrite_to_stream:...print("This is written to the stream rather than stdout")...withwrite_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 stdoutThis is also written to the stream

リエントラントな性質の実例はお互いを呼び出しあう複数の関数を含んでいる可能性が高く、したがってこの例よりもはるかに複雑です。

リエントラントであることはスレッドセーフであることと同じではない ことには注意が必要です。たとえばredirect_stdout() は、sys.stdout を異なるストリームに束縛することによりシステムの状態に対してグローバルな変更を行うことから、明らかにスレッドセーフではありません。

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

単回使用のコンテキストマネージャとリエントラントなコンテキストマネージャのいずれとも異なるタイプに "再利用可能" なコンテキストマネージャがあります (あるいは、より明確には、"再利用可能だがリエントラントでない" コンテキストマネージャです。リエントラントなコンテキストマネージャもまた再利用可能だからです)。再利用可能なコンテキストマネージャは複数回利用をサポートしますが、同じコンテキストマネージャのインスタンスがすでに with 文で使われている場合には失敗します (もしくは正しく動作しません)。

threading.Lock は再利用可能だがリエントラントでないコンテキストマネージャの例です (リエントラントなロックのためにはthreading.RLock を代わりに使う必要があります)。

再利用可能だがリエントラントでないコンテキストマネージャのもうひとつの例はExitStack です。これは現在登録されている全ての コールバック関数を、どこで登録されたかにかかわらず、呼び出します:

>>>fromcontextlibimportExitStack>>>stack=ExitStack()>>>withstack:...stack.callback(print,"Callback: from first context")...print("Leaving first context")...Leaving first contextCallback: from first context>>>withstack:...stack.callback(print,"Callback: from second context")...print("Leaving second context")...Leaving second contextCallback: from second context>>>withstack:...stack.callback(print,"Callback: from outer context")...withstack:...stack.callback(print,"Callback: from inner context")...print("Leaving inner context")...print("Leaving outer context")...Leaving inner contextCallback: from inner contextCallback: from outer contextLeaving outer context

例における出力が示すように、ひとつのスタックオブジェクトを複数の with 文で再利用しても正しく動作します。しかし入れ子にして使った場合は、一番内側の with 文を抜ける際にスタックが空になります。これは望ましい動作とは思えません。

ひとつのExitStack インスタンスを再利用する代わりに複数のインスタンスを使うことにより、この問題は回避することができます:

>>>fromcontextlibimportExitStack>>>withExitStack()asouter_stack:...outer_stack.callback(print,"Callback: from outer context")...withExitStack()asinner_stack:...inner_stack.callback(print,"Callback: from inner context")...print("Leaving inner context")...print("Leaving outer context")...Leaving inner contextCallback: from inner contextLeaving outer contextCallback: from outer context