The AI / news

Python:コンテキストマネージャはファイルを開くためだけのものではない

Python:コンテキストマネージャはファイルを開くためだけのものではない

コンテキストマネージャは、ブロック内のコードが成功しようと失敗しようと、リソースが確実にクリーンアップされることをPythonに保証させます。技術記事では、with文の下でのその仕組み、およびファイル管理をはるかに超えた、それらを構築し使用する複数の方法について詳述しています。

多くの開発者は、ファイルを自動的に閉じるwith open(path)でこの仕組みを知ります。しかし著者によれば、ファイルは最も面白くないケースです:with文は主に、その終了が保証されたブロックを区切ります。このプロトコルは、ブロック開始時に呼び出される__enter__()と、正常終了、returnによる終了、または例外による終了を問わず、ブロック終了時に呼び出される__exit__()という2つのメソッドに依存しています。

__exit__()が返す値は決定的な役割を果たします。それが真の場合、例外は抑制され、気づかれない可能性があります:プログラムは継続し、エラーは痕跡を残さずに消えます。Falseまたは値がない場合、クリーンアップ後に例外が再発生します。これが、記事で紹介されているコンテキストマネージャが決して真の値を返さない理由です。

独自のコンテキストマネージャを構築する

クラスは、呼び出し元が読み戻せる状態をマネージャが保持する必要がある場合に適しています。記事では、time.perf_counter()を使ってブロックの実行時間を計測し、ブロックが失敗した場合でも結果を表示するタイマーの例を挙げています。中断された特徴量抽出は、エラーが再発生する前に「feature extraction: 12.418s (failed)」と表示します。

線形タスク、つまりセットアップ、ブロック実行、復元の一連の流れには、デコレータ@contextmanagerを使用して単純なジェネレータ関数を書くことができます。yieldの前のすべてはブロック開始時に実行され、後のすべては終了時に実行されます。記事では、復元処理をfinally節に配置し、例外を再発生させずにtry/exceptで飲み込むことは決してしないことを推奨しています。yieldは単一の値を生成する必要があり、そうでない場合はRuntimeErrorが発生します。

状態の復元と複数リソースの管理

標準ライブラリは既にいくつかのコンテキストマネージャを提供しています。TemporaryDirectory()は一意の名前のディレクトリを作成し、ブロック終了時に削除します。これにより、前回の実行からの残留ファイルを防ぎます。Python 3.12以降、delete=Falseパラメータを使用すると、後で分析するためにファイルを保持できます。Windowsでは、開いたままのファイルがあると削除が失敗することがあります;Python 3.10以降で利用可能なignore_cleanup_errors=Trueパラメータは、問題を解決せずに緩和します。

辞書の変更には、unittest.mockモジュールのpatch.dict()が、ブロック後に元の状態を復元します:既存のキーは元の値に戻り、存在しなかったキーは削除されます。ただし記事では、os.environはプロセス全体でグローバルであることを強調しています。すべてのタスクとスレッドが変更を認識するため、並行コードでは危険です。

リソースの数が実行時までわからない場合、例えばマージするファイルの数など、ExitStackがコンテキストマネージャをグループ化します。そのenter_context()メソッドはマネージャを登録し、callback()は単純な関数を登録します。終了時、要素は逆順でポップオフされます。記事では、このポップオフはブロック終了時またはclose()の呼び出し時に行われ、オブジェクトがガベージコレクタによって削除される時ではないことを明確にしています。スタックが重くなりすぎた場合は、専用のクラスが望ましい場合があります。