Python 3.9 的 | 运算符专为字典不可变合并设计,语义明确为“右覆盖左”,优于 ** 解包的可读性、链式调用和错误提示,但不支持深度合并或旧版本兼容。

Python 3.9 引入 | 运算符不是为了“支持并集”,而是为了解决字典合并场景中长期存在的表达力与语义模糊问题——它明确表示“右覆盖左的不可变叠加”,不是数学集合操作,也不是深度合并。
为什么 | 比 {**d1, **d2} 更值得用
两者都返回新字典、不修改原对象,但关键差异在可读性、链式能力和错误提示上:
-
|是原生运算符,{**d1, **d2}是语法糖,后者在键名含非字符串时会静默失败(如{**{1: 'a'}, **{'b': 2}}报TypeError),而|对非字符串键完全兼容 - 链式写法更自然:
base | overrides | env_vars比{**base, **overrides, **env_vars}少嵌套、易定位错误位置 - 当右操作数不是
dict(比如OrderedDict或自定义映射),|的报错信息明确指出 “right operand must be a mapping”,而**解包可能抛出更晦涩的TypeError: 'X' object is not iterable
| 和 update() 到底该选哪个
核心区别就一条:是否需要保留原字典不变。
- 用
|:函数参数默认值拼接、配置分层叠加(defaults | user_config | cli_args)、构建一次性的请求 payload - 用
update()或|=:循环内反复累积数据、缓存字典复用、内存敏感场景(避免频繁创建新 dict 对象) - 注意:
d1 |= d2等价于d1.update(d2),但|=不支持右操作数为任意映射类型(仅接受dict或实现了items()的类 dict 对象),而update()兼容性更广
哪些坑最容易踩
看似简单,实际高频翻车点集中在类型、版本和嵌套逻辑上:
-
None | {}直接报TypeError: unsupported operand type(s)—— 必须先判空:(params or {}) | defaults -
list、set、str不能直接参与|运算,哪怕内容看起来像字典(如 JSON 字符串),必须显式json.loads()或dict()转换 - 嵌套字典被整层替换:
{'x': {'a': 1}} | {'x': {'b': 2}}结果是{'x': {'b': 2}},不是{'x': {'a': 1, 'b': 2}};需要深度合并请用deepmerge或手动递归 - Python 3.8 及以下版本写
d1 | d2会触发SyntaxError: invalid syntax,CI/CD 中容易漏测;若需兼容旧版本,只能退回{**d1, **d2}或封装工具函数
真正难处理的从来不是怎么写,而是什么时候不该写——比如在热路径里反复用 | 合并大字典,或误以为它能自动 flatten 嵌套结构。这些地方一旦出问题,调试成本远高于多敲几行 update()。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











