歴史的には、Pythonではリソース(ファイル、接続、トランザクション)の管理にwith構文が使用されており、これはコンテキストマネージャプロトコル(enter, exit)に基づいています。単純な場合にクラス全体を書くのは過剰なため、@contextmanagerデコレーター(contextlibモジュール)が提案されました。これにより、リソースマネージャをジェネレーターとして定義できます。
問題点 — 手動でリソースを解放または閉じることは煩雑であり、コードはエラーに対して脆弱になります(例えば、ファイルを閉じるのを忘れる)。また、簡単なこと(例えば、一時的なディレクトリの変更やstdoutの変更)のために、2つのメソッドを持つ別のクラスを書くのは望ましくありません。
解決策 — @contextmanagerを使用して、リソースの使用の「開始」と「終了」を簡潔に記述し、例外と解放を確実に処理します。
コードの例:
from contextlib import contextmanager @contextmanager def open_file(filename, mode): f = open(filename, mode) try: yield f finally: f.close() with open_file('test.txt', 'w') as f: f.write('Hello')
主な特徴:
@contextmanagerからyieldを使って複数のオブジェクトを返すことはできますか(例えば、タプルを通じて)?
はい、関連するリソースの「グループ」を返すために便利です。
コードの例:
@contextmanager def managed_two(): a, b = [], {} try: yield a, b finally: a.clear(); b.clear()
yieldの後に例外をスローするとどうなりますか — リソースは閉じますか?
はい、finallyブロックは必ず実行されます。たとえwith内のコードからエラー/例外が発生しても。
@contextmanagerは__enter__/__exit__を持つ完全なクラスのコンテキストマネージャを置き換えられますか?
ほとんどの単純なケースでは、はい。しかし、入れ子状態や継承があるより複雑なケースでは、クラスを使用する方が便利です。
手動でファイルを開いて閉じます:
f = open('test.txt', 'w') try: f.write('Hello') finally: f.close()
利点:
欠点:
@contextmanagerを使用して、一時的な作業ディレクトリの変更やファイルのオープン(または環境設定)を行います:
@contextmanager def work_in(dirname): import os prev = os.getcwd() os.chdir(dirname) try: yield finally: os.chdir(prev)
利点:
欠点: