Python 3.9+ 用 | 运算符合并字典最简洁,右结合、自动覆盖重复键、返回新字典;支持链式如 d1 | d2 | d3,但不支持 None 或非 dict 类型。

Python 3.9+ 直接用 | 运算符最简洁
如果环境支持 Python 3.9 或更新版本,| 是合并字典最直观、最不易出错的方式。它天然右结合、自动覆盖重复键,语义清晰,且返回新字典不修改原字典。
常见错误是误以为 |=(就地更新)也能链式使用——其实 |= 是就地操作,不能像 | 那样链式写成 a | b | c;而 | 每次都生成新对象,安全可链式。
-
d1 = {"a": 1, "b": 2},d2 = {"b": 3, "c": 4}→d1 | d2得{"a": 1, "b": 3, "c": 4} - 多个字典:
d1 | d2 | d3 | d4,从左到右依次覆盖,最右边的值胜出 - 注意:
|不支持None或非 dict 类型,遇到会直接报TypeError: unsupported operand type(s)
兼容旧版 Python({**d1, **d2, **d3} 展开语法
这是 Python 3.5+ 引入的解包语法,语义明确、性能好,且同样不修改原字典。但要注意展开顺序决定覆盖优先级:靠右的字典中同名键会覆盖靠左的。
容易踩的坑是误把列表或生成器直接丢进 {**...} —— 必须确保每个 **x 后面都是真正的 dict 或映射类型,否则会触发 TypeError: argument after ** must be a mapping。
立即学习“Python免费学习笔记(深入)”;
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
-
{**d1, **d2}等价于先复制d1,再逐项更新d2的键值 - 若需动态合并任意数量字典(比如来自
*args),得写成{k: v for d in dicts for k, v in d.items()},而不是试图{**dicts} - 该语法对嵌套字典不做深合并,只处理顶层键;有嵌套需求必须另写逻辑
需要深合并(嵌套键也覆盖)时别碰 update() 和 |
dict.update() 和 | 都只做浅合并:遇到嵌套字典,整个子字典被整体替换,不会递归合并内部键。例如 d1 = {"x": {"a": 1}} 和 d2 = {"x": {"b": 2}},用 | 合并后 "x" 变成 {"b": 2},"a" 彻底丢失。
真要深合并,必须手动实现或借助第三方库。标准库无内置方案,copy.deepcopy() + 递归遍历是底线做法,但要注意循环引用会导致无限递归。
- 简单深合并函数核心逻辑:对每个键,若值均为
dict,则递归合并;否则以右侧为准 - 不要依赖
json.dumps() + loads()做“伪深合并”——它会丢掉非 JSON 类型(如set、datetime) - 第三方库如
deepmerge或mergedeep提供策略控制(如“列表拼接”还是“列表替换”),但引入依赖前先确认是否真需要
性能与可读性权衡:什么时候该用 ChainMap?
collections.ChainMap 不真正合并字典,而是按顺序查找键,适合只读场景下的“逻辑合并”。它的优势是零拷贝、内存友好、构建极快;劣势是无法序列化(不是普通 dict),且修改行为不符合直觉(map["k"] = v 只影响第一个映射)。
典型误用是把它当普通字典传给期望 dict 的函数(比如 json.dumps()),会报 TypeError: Object of type ChainMap is not JSON serializable。
- 适用场景:配置分层(环境变量
os.environ覆盖默认配置)、模板上下文叠加 - 若后续要修改、序列化、或传给第三方函数,必须显式转成
dict:dict(chainmap),此时又变回浅合并 -
ChainMap查找是 O(n) 时间(n 是映射数),大量键存在时比单个dict查找慢
实际项目里,多数所谓“优雅合并”只需要浅覆盖。真正卡住人的,往往是没意识到 | 和 {**...} 都不处理嵌套,或者在需要深合并时硬套浅合并语法——结果数据莫名消失,debug 半天才发现是键被整块替换了。

















