Watch at Ease看見世界,讀懂生活。

科技 · 實用指南

Python 出現「dictionary changed size during iteration」?複製或加鎖前先證明誰改了字典

區分同迴圈修改、並行寫入、回呼重入與執行期登錄表,再選擇可驗收的語意修復。

原創圖示:字典遍歷例外到修改者證明與驗收的原創流程圖
原創示意圖:選擇快照、鎖或相依修復前先證明修改者。 Watch at Ease

RuntimeError 只表示遍歷期間映射發生結構變化,並未指出修改者是目前迴圈、其他執行緒、回呼,或 Python/相依套件維護的登錄表。

保全堆疊,證明映射所有者與修改路徑,再選擇快照、兩階段修改、共用同步或上游修復,並在原失敗交錯下驗證資料不變量。

1. 保全堆疊並確認具體映射

保存完整堆疊、Python 與套件版本、執行緒或工作名稱,以及映射的穩定識別;確認失敗框架位於自己的迴圈、deepcopy、weakref、sys.modules、執行器或相依套件。

記錄遍歷的是 d、keys、values 還是 items,並區分新增/刪除鍵與只更新既有值;更新值時呼叫的回呼仍可能改變鍵集合。

2. 一次只重現一類修改者

先停用回呼與背景寫者;若仍失敗,檢查迴圈及其呼叫的 delete、pop、clear、缺鍵 setdefault 或插入。只在負載下失敗時,為所有寫路徑記錄映射、操作、鍵雜湊、執行緒/工作與時間。

不要因堆疊落在 deepcopy 或 sorted 就認定它們發起修改;弱參照回呼、清理鉤子、比較器、記錄器、訊號或解構函式都可能重入。

3. 有意識地選擇快照或兩階段修改

若允許時間點視圖,可依語意使用 list(d)、list(d.items())、tuple(d) 或 d.copy()。淺快照只穩定鍵遍歷,不會複製可變值或保證後續複合讀取一致。

若迴圈決定刪改哪些鍵,先在不變視圖收集決定再套用;若本意是破壞性消費,明確的 popitem 迴圈更符合語意。

4. 讓並行與重入的所有權成立

多執行緒存取時,讓一致性讀取和所有結構寫入都通過同一 Lock/RLock,或交由單一執行緒持有並用佇列通訊。只鎖讀者無法約束未加鎖寫者。

回呼情境不要持任意鎖呼叫未知程式碼;在所有權邊界內取有界快照、釋放後回呼,或把副作用排至第二階段,並說明回呼看到的是快照或最新狀態。

5. 把執行期登錄表視為相依套件事故

堆疊落在 sys.modules、weakref、ProcessPoolExecutor、外掛管理器或私有登錄表時,先在受支援的新版本重現並查問題與變更記錄,保留最小重現與修復版本。

驗收需強制舊交錯,核對鍵不遺失不重複、排序假設、回呼只執行一次,並證明快照陳舊性屬預期;只是不再拋錯並不足夠。

原創圖示:同迴圈、並行、重入與執行期登錄表四類修改矩陣
原創示意圖:相同 RuntimeError 可能需要四種不同修復。 Watch at Ease