기술 · 실용 가이드
Python “dictionary changed size during iteration”? 복사나 잠금 전에 변경 주체를 증명하세요
같은 루프, 동시 쓰기, 재진입 콜백, 런타임 소유 레지스트리를 구분해 검증 가능한 수정을 선택합니다.

이 RuntimeError는 순회 중 매핑의 키 집합이 구조적으로 바뀌었다는 경계 신호일 뿐입니다. 같은 루프, 다른 스레드, 콜백, Python 또는 의존성이 소유한 레지스트리 중 누가 바꿨는지는 알려 주지 않습니다.
전체 추적을 보존하고 소유자와 변경 경로를 증명한 뒤 스냅샷, 2단계 변경, 공유 동기화 또는 상류 수정을 선택하고 실패 인터리빙에서 불변식을 검증합니다.
1. 추적과 대상 매핑을 고정합니다
전체 추적, Python·패키지 버전, 스레드·태스크 이름, 매핑 식별을 보존하고 실패 프레임이 자체 루프, deepcopy, weakref, sys.modules, executor 또는 의존성인지 확인합니다.
d, keys, values, items 중 무엇을 순회했는지와 키 추가·삭제인지 기존 값 변경인지 기록합니다. 값 갱신 중 콜백도 키 집합을 바꿀 수 있습니다.
2. 변경 주체를 한 종류씩 재현합니다
콜백과 백그라운드 쓰기를 끄고도 실패하면 루프와 호출 함수의 delete, pop, clear, 누락 키 setdefault, 삽입을 확인합니다. 부하에서만 실패하면 모든 쓰기 경로를 계측합니다.
deepcopy나 sorted 프레임이 변경을 만든 것은 아닐 수 있습니다. 약한 참조 콜백, 정리 훅, 비교자, 로거, 시그널, 소멸자가 재진입해 변경할 수 있습니다.
3. 스냅샷 또는 2단계 변경을 의도적으로 선택합니다
시점 뷰가 허용되면 의미에 맞춰 list(d), list(d.items()), tuple(d), d.copy()를 사용합니다. 얕은 스냅샷은 키 순회만 안정화합니다.
루프가 변경할 키를 결정한다면 불변 뷰에서 결정을 모은 뒤 적용합니다. 파괴적 소비가 의도라면 명시적 popitem 루프가 더 정확합니다.
4. 동시성과 재진입의 소유권을 실제로 만듭니다
여러 스레드가 접근하면 일관성이 필요한 읽기와 모든 구조 쓰기를 같은 Lock/RLock으로 보호하거나 단일 스레드 소유와 큐 통신을 사용합니다. 읽기만 잠그면 쓰기는 동기화되지 않습니다.
임의 잠금을 잡은 채 알 수 없는 콜백을 호출하지 말고, 경계 안에서 유한 스냅샷을 만든 뒤 잠금을 풀어 호출하거나 부수 효과를 다음 단계로 보냅니다.
5. 런타임 소유 레지스트리는 의존성 사고로 처리합니다
sys.modules, weakref, ProcessPoolExecutor, 플러그인 관리자 같은 내부라면 지원되는 최신 버전에서 재현하고 이슈와 변경 기록을 확인해 최소 재현과 수정 버전을 보존합니다.
기존 인터리빙을 강제하는 스트레스 테스트로 키 누락·중복, 순서 가정, 콜백 1회 실행, 의도한 스냅샷 지연을 검증합니다. 예외가 사라진 것만으로는 부족합니다.
