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

Python の「dictionary changed size during iteration」—コピーやロックの前に変更元を証明する

同一ループ、並行ライター、再入コールバック、ランタイム所有レジストリを分け、検証可能な修正を選ぶ。

辞書走査例外から変更元の証明と受け入れ確認までの図
イラスト:スナップショット、ロック、依存修正を選ぶ前に変更元を証明する。 Watch at Ease

この RuntimeError は、走査中にマッピングのキー集合が変わった境界シグナルです。同じループ、別スレッド、コールバック、Python や依存関係のレジストリのどれが変更したかまでは示しません。

トレースバックを保存し、所有者と変更経路を証明してから、スナップショット、二段階更新、共有同期、上流修正を選び、失敗したインターリーブで不変条件を検証します。

1. トレースバックと対象マッピングを固定する

完全なトレースバック、Python とパッケージの版、スレッド/タスク名、マッピングの安定した識別子を保存し、自分のループ、deepcopy、weakref、sys.modules、executor、依存関係のどこで失敗したか確認します。

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