コンテキストマネージャは、リソースの確実なファイナライズを伴うコードブロックの実行を自動化および制御することを可能にします—たとえば、ファイルを自動的に閉じたり、データベース内のトランザクションマネージャを解放したり、スレッドをロックまたはアンロックしたりします。Pythonの標準ライブラリでは、このパラダイムは、手動でファイナルステージを追跡する必要なく、外部リソースを安全かつ便利に管理するために導入されました。
歴史:
コンテキストマネージャが導入される前は、リソースを解放するためにclose()/release()メソッドを明示的に呼び出す必要があり、例外が発生する場合にエラーを引き起こす可能性がありました。with ... as ...:構文の登場により、Pythonは "リソースのスコープ"を明示的に定義することを可能にし、自動的にその初期化と完了のメソッドを呼び出します。
問題:
リソースの"閉じ方"を手動で管理するのは危険です—close()(またはrelease())を忘れると、リソースはプロセスが終了するまで使用中のままで、場合によっては永久にハングすることがあります。特にファイル、ネットワーク接続、データベース内のトランザクションを扱う場合に顕著です。
解決策:
コンテキストマネージャは、魔法のメソッド__enter__()と__exit__()を通じて実装されます。withブロックに入ると、Pythonは__enter__を呼び出し、出るときに__exit__を呼び出します。例外がブロック内で発生した場合でもです。
コード例:
class ManagedFile: def __init__(self, filename): self.filename = filename self.file = None def __enter__(self): self.file = open(self.filename, 'w') return self.file def __exit__(self, exc_type, exc_val, exc_tb): if self.file: self.file.close() # 例外を抑えるためにはreturn True、通常はreturn False with ManagedFile('demo.txt') as f: f.write('Hello, world! ') # ファイルは確実に閉じられます
主な特徴:
同じコンテキストマネージャのインスタンスを連続して異なるwithで使用することはできますか?
できません:通常、リソースは__enter__でオープンされ、__exit__で解放され、オブジェクトは再利用に不適切になります。各withブロックごとに新しいオブジェクトを作成する方が良いです。
1つのwithで複数のリソースを使用したい場合はどうすればよいですか?
コンマで変数を分けることができます:
with open('a.txt') as fa, open('b.txt') as fb: ...
または、複雑な場合はcontextlib.ExitStackを使用できます。
enter/__exit__としてクラスでコンテキストマネージャを記述するのと、デコレータ@contextmanagerを使用してジェネレータとして記述するのはどう違うのですか?
contextlibモジュールのデコレータ@contextmanagerを使用すると、yieldを介してマネージャをより簡単に実装できます:
from contextlib import contextmanager @contextmanager def open_file(name): f = open(name) try: yield f finally: f.close() with open_file('file.txt') as f: ...
開発者がopen()を介してファイルを開き、データを書き込んでclose()を呼び出すのを忘れます—ファイルは開いたままにされ、(特にエラー時に)データが書き込まれない可能性があります。
利点:
欠点:
標準(またはカスタム管理リソース)としてwith open()が常に使用され、コンテキストマネージャがリソースの解放を正しく実装します。
利点:
欠点: