Python defaultdict调用update方法时缺失键触发KeyError问题
我需要构建如下结构的三层嵌套字典:
{ 'top': { 'left': { '00': 'value_00', '01': 'value_01', # ... 更多键值对 }, 'right': { '00': 'value_00', '01': 'value_01', # ... 更多键值对 } }, 'bottom': { 'left': { '00': 'value_00', '01': 'value_01', # ... 更多键值对 }, 'right': { '00': 'value_00', '01': 'value_01', # ... 更多键值对 } } }
该字典需要满足两个规则:
- 不同键对应的
value_xx类值的数量可动态变化 - 一级键固定存在
top和bottom,但二级键left和right有时仅存在其中一个
为避免缺失键报错,我最初尝试使用defaultdict,通过.update()方法添加id: value_id键值对,以下为简化版实现代码(实际场景中值、字段数量可动态变化):
from collections import defaultdict res = defaultdict(dict) ids_list = [0, 1, 2, 3, 4, 5, 6] values_list = ['value_00', 'value_01', 'value_02', 'value_03', 'value_04', 'value_05', 'value_06'] for vert_pos in ['top', 'bottom']: for hor_pos in ['left', 'right']: for ID, val in zip (ids_list, values_list): res[vert_pos][hor_pos].update({ID: val}) print(res)
运行代码时触发如下报错:
res[vert_pos][hor_pos].update({ID: val}) KeyError: 'left'
即便使用了defaultdict,调用update()方法时仍要求键已存在。我尝试改用直接赋值写法:
res[vert_pos][hor_pos] = {ID: val}
该写法不会触发报错,但最终字典仅保留最后一组ID:value键值对,输出结果如下:
defaultdict(<class 'dict'>, {'top': {'right': {6: 'value_06'}}, 'bottom': {'right': {6: 'value_06'}}})
我还尝试过如下写法:
res.update({vert_pos: {hor_pos: {id: val}}})
但该写法同样仅保留最后一组ID:value键值对,无法得到预期的三层嵌套字典结构。
报错核心原因是你仅将最外层res定义为defaultdict(dict):访问res[vert_pos]时,defaultdict确实会自动生成普通dict作为值,但这个普通dict没有自动创建缺失键的能力,后续访问res[vert_pos][hor_pos]时,普通dict找不到对应键自然抛出KeyError。
两种可直接落地的实现方案:
方案1:嵌套定义defaultdict
给第二层也配置自动生成dict的逻辑,完全适配二级键动态增减的需求,不需要提前初始化所有键:
from collections import defaultdict # 两层defaultdict,第三层为普通字典 res = defaultdict(lambda: defaultdict(dict)) ids_list = [0, 1, 2, 3, 4, 5, 6] values_list = ['value_00', 'value_01', 'value_02', 'value_03', 'value_04', 'value_05', 'value_06'] for vert_pos in ['top', 'bottom']: for hor_pos in ['left', 'right']: # 直接批量生成三级字典,无需逐键循环update res[vert_pos][hor_pos].update(dict(zip(ids_list, values_list))) # 如需转为普通原生dict可添加这行 res = {k: dict(v) for k, v in res.items()} print(res)
运行输出完全符合预期结构,未访问过的二级键不会自动生成,适配「left/right可能仅存在一个」的需求。
方案2:原生dict + setdefault
不想依赖collections模块的话,用原生字典的setdefault方法可以实现完全相同的效果:
res = {} ids_list = [0, 1, 2, 3, 4, 5, 6] values_list = ['value_00', 'value_01', 'value_02', 'value_03', 'value_04', 'value_05', 'value_06'] for vert_pos in ['top', 'bottom']: for hor_pos in ['left', 'right']: # 逐层检查键是否存在,不存在则初始化为空dict res.setdefault(vert_pos, {}).setdefault(hor_pos, {}).update(dict(zip(ids_list, values_list))) print(res)
之前直接赋值、外层update写法失效的原因很简单:每次循环都会把对应层级的字典整体替换为仅含当前单键值对的新字典,之前写入的内容被全量覆盖,最终只会保留最后一次循环写入的内容。
内容的提问来源于stack exchange,提问作者Carlo

