科技 · 實用指南
Python 出現「dictionary changed size during iteration」?複製或加鎖前先證明誰改了字典
區分同迴圈修改、並行寫入、回呼重入與執行期登錄表,再選擇可驗收的語意修復。

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、外掛管理器或私有登錄表時,先在受支援的新版本重現並查問題與變更記錄,保留最小重現與修復版本。
驗收需強制舊交錯,核對鍵不遺失不重複、排序假設、回呼只執行一次,並證明快照陳舊性屬預期;只是不再拋錯並不足夠。
