29.6. contextlib
--- with
文コンテキスト用ユーティリティ¶
ソースコード: Lib/contextlib.py
このモジュールは with
文に関わる一般的なタスクのためのユーティリティを提供します。詳しい情報は、 コンテキストマネージャ型 と with文とコンテキストマネージャ を参照してください。
29.6.1. ユーティリティ¶
以下の関数とクラスを提供しています:
-
@
contextlib.
contextmanager
¶ この関数は
with
文コンテキストマネージャのファクトリ関数を定義するために利用できる デコレータ です。新しいクラスや__enter__()
と__exit__()
メソッドを別々に定義しなくても、ファクトリ関数を定義することができます。A simple example (this is not recommended as a real way of generating HTML!):
from contextlib import contextmanager @contextmanager def tag(name): print("<%s>" % name) yield print("</%s>" % name) >>> with tag("h1"): ... print("foo") ... <h1> foo </h1>
デコレート対象の関数は呼び出されたときに ジェネレータ-イテレータを返す必要があります。このイテレータは必ず値を1つ yield しなければなりません。
with
文のas
節が存在するなら、その値は as 節のターゲットへ束縛されることになります。ジェネレータが yield を実行した箇所で
with
文のネストされたブロックが実行されます。ブロックから抜けた後でジェネレータは再開されます。ブロック内で処理されない例外が発生した場合は、ジェネレータ内部の yield を実行した箇所で例外が再送出されます。なので、(もしあれば) エラーを捕捉したり、クリーンアップ処理を確実に実行したりするために、try
...except
...finally
構文を使用できます。例外を捕捉する目的が、(完全に例外を抑制してしまうのではなく) 単に例外のログをとるため、もしくはあるアクションを実行するためなら、ジェネレータはその例外を再送出しなければなりません。例外を再送出しない場合、ジェネレータのコンテキストマネージャはwith
文に対して例外が処理されたことを示し、with
文の直後の文から実行を再開します。contextmanager()
はContextDecorator
を使っているので、contextmanager()
で作ったコンテキストマネージャはwith
文だけでなくデコレータとしても利用できます。デコレーターとして利用された場合、新しい generator インスタンスが関数呼び出しのたびに暗黙に生成されます (このことによって、contextmanager()
によって作られたなにがしか「単発」コンテキストマネージャを、コンテキストマネージャがデコレータとして使われるためには多重に呼び出されることをサポートする必要がある、という要件に合致させることが出来ます。)バージョン 3.2 で変更:
ContextDecorator
の使用。
-
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.
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 anio.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 redirectingsys.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
バージョン 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 に、例外が起こらなかった場合の引数が渡されます。
-
29.6.2. 例とレシピ¶
このセクションでは、 contextlib
が提供するツールの効果的な使い方を示す例とレシピを紹介します。
29.6.2.1. 可変数個のコンテキストマネージャーをサポートする¶
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
文を使って簡単に行えるようにします。
29.6.2.2. 1つのオプションのコンテキストマネージャーを簡潔にサポートする¶
1つのオプションのコンテキストマネージャーを使う場合、 ExitStack
を何もしないコンテキストマネージャーとして利用することで、ソースコードの構造を変更することなく1つのコンテキストマネージャーを省略することができます:
def debug_trace(details):
if __debug__:
return TraceContext(details)
# Don't do anything special with the context in release mode
return ExitStack()
with debug_trace():
# Suite is traced in debug mode, but runs normally otherwise
29.6.2.3. __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
文を使って処理することができない様々なシチュエーションの処理をすることができます。
29.6.2.4. __enter__
実装内のクリーンアップ¶
ExitStack.push()
のドキュメントで言及したとおり、このメソッドはすでに獲得したリソースを、 __enter__()
メソッドの残りのステップが失敗した時にクリーンアップするために利用することができます。
次の例では、リソースの確保と開放の関数に加えて、オプションのバリデーション関数を受け取るコンテキストマネージャーで、この方法を使ってコンテキストマネージャープロトコルを提供しています:
from contextlib import contextmanager, ExitStack
class ResourceManager:
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()
29.6.2.5. 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()
Due to the way the decorator protocol works, a callback function declared this way cannot take any parameters. Instead, any resources to be released must be accessed as closure variables.
29.6.2.6. コンテキストマネージャーを関数デコレーターとして使う¶
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: {}'.format(self.name))
def __exit__(self, exc_type, exc, exc_tb):
logging.info('Exiting: {}'.format(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
文を使う必要があります。
29.6.3. 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
29.6.3.1. 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.
29.6.3.2. 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