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 で追加.

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)

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

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

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

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

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

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

バージョン 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 で追加.

contextlib.chdir(path)

現在の作業ディレクトリを変更するパラレル非安全なコンテキストマネージャです。グローバルな状態である作業ディレクトリを変更するため、ほとんどのマルチスレッドまたは非同期のコンテキストに対する利用は適切ではありません。また、プログラムの実行権限を一時的に放棄するジェネレータのような、直線的でないコードを実行する場合も適切ではありません -- 明確に必要でないかぎり、このコンテキストマネージャがアクティブな状態で yield すべきではありません。

これは chdir() の単純なラッパーで、コンテキストに入るときに現在の作業ディレクトリを変更し、終了時に元の作業ディレクトリを復元します。

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

バージョン 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 文を関数内で利用するべきです。

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

バージョン 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 です。アプリケーション独自のより高レベルなコンテキストマネージャーを作るための基板として使うのに適しています。

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

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