テクノロジー · 実用ガイド

Windows の Python NamedTemporaryFile:再オープンと確実な削除

最初の PermissionError と後の削除失敗は原因が異なります。削除境界を決め、すべての二次ハンドルをその前に閉じます。

Windows の三つの削除方式を比較する独自マトリクス。
独自技術イラスト:独自技術イラスト:他プログラムへ渡す前に削除境界を選びます。 Watch at Ease

Windows の NamedTemporaryFile は二つの時点で失敗します。既定値では別プログラムがパスを開けず、設定変更後は読めてもハンドルが残り、コンテキスト終了時の削除が失敗することがあります。

一時パスを所有権の受け渡しとして扱います。削除を close、コンテキスト終了、finally のどこで行うかを先に決め、利用者をその境界までに完了させます。

1. どの PermissionError かを切り分ける

サブプロセス、ブラウザー、アップロードライブラリがパスを開けないなら、作成側の Windows ハンドルと二つ目のオープンの共有条件が一致していません。CPython は Windows で既定の delete=True、delete_on_close=True に O_TEMPORARY を使いますが、組み込み open や多くの外部プログラムは同じ削除共有を要求しません。

利用者は読めたのにコンテキスト終了や os.unlink で失敗するなら、別の生存ハンドルを探します。Microsoft によれば共有モードはハンドルが閉じるまで続き、削除は互換ハンドルを待ちます。フラグ変更だけではアップロードストリームやサブプロセスを閉じられません。

2. Python 3.12+ ではコンテキスト終了時削除を選ぶ

通常の外部オープナーには with NamedTemporaryFile(delete=True, delete_on_close=False) as tmp: を使います。tmp を開いたまま読ませるなら flush、排他アクセスが必要ならブロック内で close します。パスは with の終了まで残り、その時点で Python が os.unlink を試みます。

ブロックを出る前にサブプロセスを待ち、アップロード用に open した全ファイルを閉じます。削除共有しない通常の二次オープンは必ず先に閉じます。ディレクトリに削除権限がなければ PermissionError は残るため、実運用ディレクトリで検証します。

3. 古い Python では finally で削除する

Python 3.12 より前、またはパスを渡す前にラッパーを閉じる必要がある場合は delete=False を使います。NamedTemporaryFile を閉じ、利用者を呼び、finally でパスを削除します。delete=False では delete_on_close は無効で、清理は全面的にアプリの責任です。

作成成功後にだけパスを保存し、finally では二次ストリームを閉じてから os.unlink を呼びます。tempfile.mktemp は安全な作成をせず競合を生むため、この問題の回避策にしてはいけません。

4. ハンドルの全ライフサイクルを監査する

受け渡し前に書き込みを flush するか必要に応じて作成ハンドルを閉じ、パスの存在を確認します。利用中は非同期に起動してすぐブロックを出ず、完了を待ちます。ファイルオブジェクトを受け取るライブラリでは open(tmp.name, 'rb') 自体を with で囲みます。

削除前にファイルオブジェクト、サブプロセス、メモリマップを確認します。削除後はパスが消えたことを確認し、失敗は正確に表面化させます。再試行は未閉鎖ハンドルを隠すだけで、所有者の特定にはなりません。

5. 移植性の境界を明示する

POSIX は開いたままの名前付き一時ファイルを通常再オープンできますが、Windows の設計が安全だという証拠にはなりません。全プラットフォームで同じ明示的な受け渡しと削除順序を使い、macOS/Linux のテストだけ通る状態を避けます。

既定の close 時削除は全オープナーが削除共有する場合に強く、コンテキスト終了時削除は普通のプログラムに渡しやすい一方で中断され得ます。delete=False は互換性が高い代わりに finally と起動時の残骸清掃が必要です。利用者失敗と削除失敗の両方をテストします。

作成、外部利用、削除を示す独自フロー。
独自技術イラスト:独自技術イラスト:互換しない二次ハンドルは削除前にすべて閉じます。 Watch at Ease