Python中两种字典迭代异常的差异及触发原理探究
Python字典迭代时修改引发的两种异常差异与触发机制
两种异常的直观差异
- 触发条件不同
dictionary changed size during iteration:迭代过程中字典的键值对总数(大小)发生了变化(比如新增/删除键值对导致总数增减)。dictionary keys changed during iteration:字典大小未变,但底层键集合的哈希结构或成员构成发生了变更(比如删除旧键同时新增新键,总数不变但键的哈希分布变了)。
- 触发时机不同
- 前者只要字典大小一变,下一次迭代步骤就会立刻报错(比如你实验里打印第一个键后就触发)。
- 后者因为大小没变化,迭代器会先继续遍历已有的槽位,直到检测到底层结构版本变更时才报错(这就是你实验里能打印7个键的原因)。
内部触发机制
Python字典的迭代器(包括keys()、items()、values()返回的迭代器)依赖字典的两个核心状态变量来保证遍历的一致性:
ma_used:记录字典当前的键值对总数,也就是哈希表中已使用的槽位数量。ma_version_tag:字典的结构版本号,每次字典的哈希表结构发生变更(比如新增/删除键、扩容),这个版本号都会自动递增。
当迭代开始时,迭代器会先记录下这两个变量的初始值,之后每次迭代步骤都会做两次检查:
1. 新增键值对触发的异常逻辑
当你新增键值对时,ma_used会直接增加。迭代器在准备获取下一个键之前,会先对比当前字典的ma_used和初始记录值——一旦发现不一致,立刻抛出dictionary changed size during iteration异常。
这是因为字典大小变化通常意味着哈希表可能已经扩容(或者收缩),原有的遍历索引逻辑完全失效,继续迭代会导致数据丢失、重复遍历等不可预测的结果,所以Python直接终止迭代。
2. 删旧键加新键触发的异常逻辑
这种操作下,ma_used保持不变,所以第一次检查(大小对比)会通过。但删除旧键会清空哈希表中对应的槽位,新增新键会把新的哈希值映射到某个槽位(可能是旧槽位也可能是新的),这会触发ma_version_tag递增。
迭代器在每次获取到键之后,还会对比当前字典的ma_version_tag和初始值。当迭代到某个步骤时,发现版本号不一致,就会抛出dictionary keys changed during iteration异常。
此时迭代器已经意识到底层哈希结构被修改,继续遍历会出现不可预测的遍历结果,因此终止迭代并报错。你实验中能打印7个键,是因为前7个槽位的键在版本号变更前已经被迭代器访问完成,直到第8次迭代时才检测到版本号变化。
内容的提问来源于stack exchange,提问作者THaruka
相关产品推荐
相关产品推荐

