Python循环中修改字典键导致遍历异常的解析与解决方法

 更新时间:2026年06月28日 15:47:30   作者:站大爷IP  
这篇文章主要介绍了在线典遍历修改键值的Bug分析与解决策略,探讨Python字典迭代限制、安全方案及实战技巧,避免线上环境出错,需要的朋友可以参考下

一个让我在线上环境翻车的Bug

去年双十一前夕,我们有个订单统计系统,需要在遍历订单字典时,根据某些规则重新整理订单数据。代码大概是这样的:

orders = {
    "A001": {"status": "paid", "amount": 299},
    "A002": {"status": "unpaid", "amount": 150},
    "A003": {"status": "paid", "amount": 399},
}

# 把状态为paid的订单统一改个编号前缀
for key in orders.keys():
    if orders[key]["status"] == "paid":
        new_key = f"PAID_{key}"
        orders[new_key] = orders.pop(key)

运行时直接报错:

RuntimeError: dictionary changed size during iteration

我当时就卡住了:为什么不能改?我只是修改键名而已,又没有增加或减少元素数量。改完一个删一个,字典大小保持不变,为什么不让遍历继续?

后来我仔细研究了Python字典的底层实现,才明白为什么会有这个限制。今天把这些理解讲清楚,顺便聊聊那些"变通方案"背后的隐患。

第一步:先搞清楚错误是怎么触发的

这个错误不仅仅是"Python不准你这样做"那么简单。它背后有个重要的设计考量:字典在迭代过程中需要保持内部结构的一致性

先看几个会触发错误的典型操作:

d = {"a": 1, "b": 2, "c": 3}

# 操作1:直接遍历并删除
for key in d:
    if key == "b":
        del d[key]   # RuntimeError

# 操作2:用keys()遍历并删除
for key in d.keys():
    if key == "b":
        del d[key]   # RuntimeError

# 操作3:遍历时新增键
for key in list(d.keys()):
    d[f"{key}_new"] = d[key]   # RuntimeError

# 操作4:遍历时修改键名(先增后删)
for key in d.keys():
    new_key = f"new_{key}"
    d[new_key] = d.pop(key)   # RuntimeError

以上四种操作,Python都会禁止。

但有一个例外:在遍历时修改现有键的值,是允许的

for key in d:
    d[key] = d[key] * 2   # 可以,值变了,但键和大小都没变

为什么改值可以,改键(增删)就不行?因为改值不改变字典的结构(哈希表的大小和排列),而增删键会触发字典的重新哈希表变化,导致迭代器失效。

第二步:字典的底层结构——一张不断扩大的桌子

要理解为什么不能一边遍历一边改,得先了解Python字典的底层实现。

Python字典本质上是一张哈希表。你可以想象成一个大桌子,桌子上有N个位置(槽位),每个键通过哈希算法算出一个数字,然后放到对应的槽位上。

当字典里的元素太多,桌子上的位置不够用时,字典会扩容——换一张更大的桌子,把所有元素重新放一遍(这个操作叫rehash)。

这就带来两个问题:

  1. 迭代过程中如果扩容了,迭代器正在遍历"旧桌子",但字典已经换成了"新桌子",迭代器就找不到原来的位置了。
  2. 即使不扩容,删除一个键会让某个槽位变成"空洞",迭代器的内部指针可能指向一个空槽,导致遗漏或重复。

为了安全和简单,Python设计者决定:一旦字典在迭代期间发生变化(增删键),直接抛出异常

第三步:三个合法但各有利弊的绕坑方案

既然不能直接改,那怎么达到"修改键"的目的呢?有三种常见方案,各有优缺点。

方案1:把键复制成列表

这是最简单、最直观的做法。

orders = {
    "A001": {"status": "paid", "amount": 299},
    "A002": {"status": "unpaid", "amount": 150},
    "A003": {"status": "paid", "amount": 399},
}

for key in list(orders.keys()):   # 复制一份键列表
    if orders[key]["status"] == "paid":
        new_key = f"PAID_{key}"
        orders[new_key] = orders.pop(key)

print(orders)
# {'A002': {'status': 'unpaid', 'amount': 150}, 'PAID_A001': {'status': 'paid', 'amount': 299}, 'PAID_A003': {'status': 'paid', 'amount': 399}}

list(orders.keys())会生成一个独立的列表,包含字典当前所有的键。迭代的是这个列表,而不是字典本身,所以字典在迭代过程中怎么改都没事。

优点:简单、安全、代码可读性好。 缺点:需要复制一份键列表,如果字典很大(百万级键),复制会占用额外内存和时间。

方案2:创建新字典

不修改原字典,而是构造一个新字典。

orders = {
    "A001": {"status": "paid", "amount": 299},
    "A002": {"status": "unpaid", "amount": 150},
    "A003": {"status": "paid", "amount": 399},
}

new_orders = {}
for key, value in orders.items():
    if value["status"] == "paid":
        new_key = f"PAID_{key}"
    else:
        new_key = key
    new_orders[new_key] = value

orders = new_orders
print(orders)

优点:没有修改原字典,更安全;适合函数式编程风格。 缺点:同样需要额外内存,而且如果字典很大,复制开销不小。

方案3:使用collections.OrderedDict或遍历顺序控制(Python 3.7+天然有序)

在Python 3.7+,字典天然有序。如果你利用这个特性,配合方案2,可以保证新字典的顺序符合预期。

不要尝试在迭代时使用for key in d:再加del或新增,无论版本多少都报错。

第四步:更深层的坑——修改值也会"牵连"键吗?

d = {"a": 1}
for key in d:
    d[key] = d[key] + 1   # 改值,安全

改值安全,是因为没有触发表结构变化。

但有一种情况要小心:如果值是可变对象,修改它不会触发表结构变化,但可能影响后续逻辑

d = {"a": [1, 2, 3]}
for key in d:
    d[key].append(4)   # 列表内容变了,但字典结构没变,安全

第五步:实战——过滤字典中不符合条件的键

一个常见的需求:删除字典中所有值小于5的键。

# 错误写法
d = {"a": 1, "b": 2, "c": 3, "d": 4}
for key in d:
    if d[key] < 5:
        del d[key]   # RuntimeError

正确写法:

# 方法1:复制键列表
for key in list(d.keys()):
    if d[key] < 5:
        del d[key]

# 方法2:字典推导式
d = {key: value for key, value in d.items() if value >= 5}

第六步:两种错误的"偷懒"写法及其后果

错误1:在循环中用.pop()删除并判断

d = {"a": 1, "b": 2, "c": 3}
for key in d:
    if d[key] % 2 == 0:
        del d[key]   # RuntimeError

错误2:在循环中用新的键覆盖旧键

d = {"A": 1, "B": 2}
for key in d:
    if key == "A":
        d["A_new"] = d.pop("A")   # RuntimeError

这两种都会触发报错,而且很难通过日志定位。

第七步:利用while循环配合popitem?别试!

有些人想到用while d:popitem()处理:

d = {"a": 1, "b": 2}
while d:
    key, value = d.popitem()
    # 处理...

这样虽然不会报错,但popitem()会随机弹出键值对(实际上按后进先出顺序),你很难控制处理顺序。

一张表总结

操作是否安全原因
遍历时修改键的值✅ 安全不改变表结构
遍历时修改可变值的内容✅ 安全不改变表结构
遍历时删除键❌ 报错改变表大小
遍历时新增键❌ 报错可能触发rehash
遍历时修改键名(先删后增)❌ 报错相当于删+增
复制键列表后遍历修改✅ 安全迭代的是独立列表
创建新字典后赋值✅ 安全原字典没被修改
用字典推导式创建新字典✅ 安全不修改原字典

最后的建议

如果你遇到"在遍历字典时需要修改键"的问题,按这个优先级选择:

  1. 首选:用字典推导式创建新字典,除非你特别在意内存。
  2. 次选:复制键列表list(d.keys()),代码直接、可读性好。
  3. 不要做:直接在遍历原字典时增删键,也别试图用whilepopitem()控制顺序。

记住这个原则:迭代器迭代的是字典当前的"视图",迭代期间改变视图本身,迭代器就失效了

那次双十一之后,我把所有类似代码都改成了"先收集要修改的键,统一处理"的模式。从那以后,再没因为改字典键出过线上事故。

以上就是Python循环中修改字典键导致遍历异常的解析与解决方法的详细内容,更多关于Python修改字典键导致遍历异常的资料请关注脚本之家其它相关文章!

相关文章

  • python队列queue模块详解

    python队列queue模块详解

    这篇文章主要为大家详细介绍了python队列queue模块的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-04-04
  • Django应用程序入口WSGIHandler源码解析

    Django应用程序入口WSGIHandler源码解析

    这篇文章主要介绍了Django应用程序入口WSGIHandler源码解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-08-08
  • 通过代码实例了解Python异常本质

    通过代码实例了解Python异常本质

    这篇文章主要介绍了通过代码实例了解Python1异常本质,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • pygame实现俄罗斯方块游戏(AI篇1)

    pygame实现俄罗斯方块游戏(AI篇1)

    这篇文章主要为大家详细介绍了pygame实现俄罗斯方块游戏AI的第1篇,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-10-10
  • python代码的几种常见加密方式分享

    python代码的几种常见加密方式分享

    这篇文章主要介绍了python代码的几种常见加密方式分享,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下
    2022-07-07
  • python实践项目之监控当前联网状态详情

    python实践项目之监控当前联网状态详情

    介绍一个利用Python监控当前联网状态情况的python代码,它可以清楚地知道,你的电脑网络是否是链接成功或失败,下面小编带大家来一起学习它
    2019-05-05
  • Python and、or以及and-or语法总结

    Python and、or以及and-or语法总结

    这篇文章主要介绍了Python and、or以及and-or语法总结,本文分别给出实例讲解它们的使用方法,需要的朋友可以参考下
    2015-04-04
  • 在Django中输出matplotlib生成的图片方法

    在Django中输出matplotlib生成的图片方法

    今天小编就为大家分享一篇在Django中输出matplotlib生成的图片方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-05-05
  • Python pandas数据合并merge函数用法详解

    Python pandas数据合并merge函数用法详解

    这篇文章主要给大家介绍了关于Python pandas数据合并merge函数用法的相关资料,数据分析中经常会遇到数据合并的基本问题,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2023-07-07
  • python PIL中ImageFilter模块图片滤波处理和使用方法

    python PIL中ImageFilter模块图片滤波处理和使用方法

    这篇文章主要介绍PIL中ImageFilter模块几种图片滤波处理和使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-11-11

最新评论