Python 中如何避免因字典在迭代时被修改而引发的 RuntimeError

用的 web.py 搞了一个 app,有的时候个别接口如果点的太快会出现标题里面的报错,跳出去重新点就没事了,影响倒是没有,就是看着很不爽,求一个解决版本
Python 中如何避免因字典在迭代时被修改而引发的 RuntimeError

11 回复

你这样鬼知道什么问题啊
看报错就是你进行迭代的时候对字典进行了增加或者减少操作, 例如:
a = {1: 2, 3: 4, 5: 6, 7: 8}

for x in a:
a.pop(1)

就会报这个错


这个问题很常见,直接原因是你在用 for key in my_dict: 循环时,又对同一个字典执行了 del my_dict[key]my_dict.pop(key) 这类修改大小的操作,Python 的字典迭代器会立刻抛出一个 RuntimeError: dictionary changed size during iteration

根本原因:Python 的字典迭代器在内部维护了一个版本号,每次字典结构发生变化(如增删键)这个版本号都会改变。迭代器在每次取下一个元素时都会检查这个版本号是否和开始迭代时一致,不一致就报错。这是一种安全保护机制,防止你在迭代时漏掉或重复处理元素。

解决方案:核心思路就一个——把要迭代的键和要修改的操作分开。别在迭代原始字典的同时删它。

方法一:迭代键的副本(最常用) 直接对 list(my_dict.keys()) 进行迭代。这样即使你在循环里删原字典,迭代的也是之前固定的键列表。

my_dict = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
for key in list(my_dict.keys()):  # 关键在这里:list()创建了键的副本
    if my_dict[key] % 2 == 0:
        del my_dict[key]  # 安全删除
print(my_dict)  # 输出: {'a': 1, 'c': 3}

方法二:记录要删除的键,循环后统一处理 如果删除逻辑复杂或者字典很大,避免在循环内多次修改,可以先记下来。

my_dict = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
keys_to_delete = []
for key, value in my_dict.items():
    if value % 2 == 0:
        keys_to_delete.append(key)
for key in keys_to_delete:
    del my_dict[key]
print(my_dict)  # 输出: {'a': 1, 'c': 3}

方法三:字典推导式创建新字典(Pythonic 写法) 如果你是想根据条件筛选字典项,直接建个新的更清晰。

my_dict = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
my_dict = {k: v for k, v in my_dict.items() if v % 2 != 0}
print(my_dict)  # 输出: {'a': 1, 'c': 3}

简单总结:别边循环边改字典大小,用 list(dict.keys()) 或者字典推导式就能搞定。

字面上看,像是在 for 循环的时候,字典被修改了(新增? pop ?)

先拷贝一份再使用? and 不要随意修改多个线程访问的数据

建议:
1. 如果经常修改,不要用全局的字典(请求间隔离)
2. 如果需要共享,建议加锁

我初步估计就是如 4 楼说的一样,用了一个字典作为全局变量,一并发就挂了。最无脑就是加锁罗

这不是 py 的锅,我不背

这是多进程字典被修改时不要去 for 它,你可以用 multiprocessing Manager dict 复现这个问题。要么加锁要么使用前弄个 copy。而且边写边读很难 debug

经典的迭代器失效问题,如上加锁或者局部字典

根据楼上
for i in x.copy():

谢谢 说到迭代器失效 我发现我代码里面有字典用字典生成式写的 字典生成式好像引用了迭代器原理 我已经放假了 等回去后把字典生成式替换了试一下

回到顶部