科技 · 實用指南
Windows 上的 Python NamedTemporaryFile:可重開也能清理
第一次 PermissionError 與之後的刪除失敗原因不同。先決定刪除邊界,再讓所有次要控制代碼在邊界前關閉。

NamedTemporaryFile 在 Windows 可能於兩個時點失敗:預設設定下,另一程式無法開啟路徑;修改旗標後雖可讀取,卻可能留下控制代碼,使內容管理器退出時清理失敗。
把暫存路徑視為所有權交接。先決定刪除發生在檔案關閉、內容管理器退出或自己的 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;若它需要獨占存取則在 with 區塊內先 close。路徑會保留到區塊退出,屆時 Python 嘗試 os.unlink。
離開區塊前等待子程序完成,並關閉上傳時另外 open 的每個檔案物件。未共享刪除權限的普通第二開啟必須先關閉。若目錄沒有刪除權限,unlink 仍會拋出 PermissionError,應在實際部署目錄驗證而非忽略。
3. 舊版 Python 用 finally 清理
Python 3.12 以前,或函式庫要求先關閉包裝器再交出路徑時,使用 delete=False。關閉 NamedTemporaryFile、呼叫使用者,再於 finally 刪除路徑。delete=False 會讓 delete_on_close 無效,因此清理完全由你負責。
僅在建立成功後保存路徑;finally 中先關閉所有次要串流,再呼叫 os.unlink。不要用 tempfile.mktemp 迴避問題:它只傳回未安全建立的名稱,會帶來 NamedTemporaryFile 原本要避免的競爭條件。
4. 稽核完整控制代碼生命週期
交接前確認寫入已 flush,或依使用者需要關閉建立控制代碼,並確認路徑存在。交接期間要等待使用者完成,不能非同步啟動後立即退出內容管理器。若函式庫接收檔案物件,將 open(tmp.name, 'rb') 放在自己的 with 區塊內,讓上傳控制代碼確定關閉。
清理前列出程式持有的檔案物件、子程序與記憶體映射。清理後確認路徑消失,否則揭露準確錯誤。重試可能只掩蓋未關閉控制代碼,不能代替確認誰仍持有檔案。
5. 明確保留可攜性邊界
POSIX 通常允許在命名暫存檔仍開啟時重開,但不能據此證明 Windows 生命週期安全。各平台都採同一個明確交接與清理順序,避免測試在 macOS 或 Linux 通過、正式環境卻在 Windows 失敗。
三種選擇的崩潰行為不同:預設關閉即刪在所有開啟者共享刪除時最可靠;內容管理器退出清理較適合一般程式但可能中斷;delete=False 相容性最高,卻需要 finally 與啟動時清掃遺留檔案。程式碼審查應記錄選擇並測試使用者和清理失敗。
