Python 3.9+ 推荐用 | 合并字典,返回新字典、右操作数优先覆盖;3.5–3.8 用 {d1, d2} 兼容性好;update() 是就地浅合并,不适用深度合并或需保留原字典场景。

Python 3.9+ 直接用 | 运算符合并字典
这是目前最简洁、推荐的写法,原生支持、可读性强、且返回新字典,不修改原对象。
常见错误是误以为 | 是“就地更新”,其实它和 + 类似,只生成新对象;另外,若字典含不可哈希键(比如列表),会直接报 TypeError: unhashable type,不是合并失败而是键本身非法。
-
d1 = {"a": 1, "b": 2},d2 = {"b": 3, "c": 4}→d1 | d2得{"a": 1, "b": 3, "c": 4}(右操作数优先) - 嵌套字典不会递归合并:
{"x": {"y": 1}} | {"x": {"z": 2}}结果是{"x": {"z": 2}},旧值被完全覆盖 - 不支持
dict | list或dict | None,类型必须都是dict
Python 3.5–3.8 用 {**d1, **d2} 解包语法
这是兼容性最好、语义清晰的方案,原理是字典字面量解包,行为和 | 一致:右键覆盖左键、生成新字典、不修改源。
容易踩的坑是误在函数调用中滥用解包,比如 func(**d1, **d2) 没问题,但若 d1 和 d2 都含同名关键字参数,会触发 TypeError: got multiple values for argument —— 这是调用层问题,不是字典合并本身出错。
立即学习“Python免费学习笔记(深入)”;
- 支持任意数量字典:
{**d1, **d2, **d3} - 键必须是字符串(或可转为字符串的);如果
d2的键是1(int),会报SyntaxError,因为字面量要求 key 是 identifier 或 string literal - 性能略低于
|(多一次解析和构造),但日常使用无感
需要就地更新时,别用 update() 简单覆盖
dict.update() 看似方便,但它直接修改左操作数字典,且对嵌套结构完全无能为力——它只是浅合并。
典型误用场景:想“把配置 defaults 和 user_config 合并成最终配置”,却写 defaults.update(user_config),结果破坏了原始默认值,后续其他地方复用 defaults 就出问题。
- 如需就地更新又保留原字典,先
copy():final = defaults.copy(); final.update(user_config) - 若要深度合并(比如
{"db": {"host": "a"}}和{"db": {"port": 5432}}合成{"db": {"host": "a", "port": 5432}}),update()不行,得手写递归或用deepmerge等第三方库 -
update()接受映射或可迭代对,但传入非 dict(如 list of tuples)时,容易因格式错导致静默覆盖或 KeyError
老版本 Python(collections.ChainMap
没有解包、没有 |,最稳妥的是 dict(d1, **d2)(仅限 d2 键全为字符串),否则就得写循环。而 ChainMap 是个陷阱:它不真合并,只是逻辑上“链式查找”,一旦原字典被改,结果立刻变;而且 ChainMap 不是 dict 子类,很多期望 dict 行为的地方(比如 JSON 序列化、某些 ORM)会报错。
-
dict(d1, **d2)要求d2所有键是字符串,否则抛TypeError: keywords must be strings -
ChainMap(d1, d2)["key"]查找顺序是 d1→d2,但len()、.keys()只返回 d1 的,行为不符合直觉 - 真正要“一次性合并成 dict”,还是老实用
for k, v in d2.items(): d1[k] = v(注意是否允许修改 d1)
合并看似简单,但版本差异、深浅层级、是否就地、键类型合法性,四个维度一叠加,很容易掉进静默覆盖或运行时报错的坑里。最安全的底线是:只要不明确需要就地修改,一律用 |(3.9+)或 {**d1, **d2}(3.5+),别碰 update() 和 ChainMap 做合并用途。









