Python 3.9 的 | 运算符合并两个字典的键值对,返回新字典,右字典值覆盖左字典同名键,不修改原字典,不支持嵌套合并,仅适用于 Python 3.9+。

Python 3.9 的 | 运算符到底合并什么
它合并的是两个 dict 的键值对,返回一个新字典,**不修改原字典**。行为等价于 {**d1, **d2},但更清晰、支持链式调用,且对重复键有明确定义:右边字典的值覆盖左边。
常见误判是以为它做“并集”(数学意义),其实它是“右优先覆盖的键值叠加”。比如:
d1 = {'a': 1, 'b': 2}
d2 = {'b': 3, 'c': 4}
d3 = d1 | d2 # {'a': 1, 'b': 3, 'c': 4} —— 注意 'b' 被 d2 覆盖了
- 不能用于
dict和非dict类型(如list或set)直接运算,会报TypeError: unsupported operand type(s) - 左操作数必须是
dict;右操作数可以是任意映射类型(如collections.OrderedDict),但必须实现items() - 空字典参与运算安全:
{} | d等价于d.copy()(浅拷贝)
什么时候该用 |,而不是 update() 或 ** 展开
核心判断依据是:是否需要**不可变地构造新字典**,且逻辑上是“基础配置 + 覆盖配置”这类场景。
- 用
|:函数参数默认值合并、API 请求参数拼接、环境配置分层叠加(如 base + dev) - 别用
|:需要原地修改(用d1.update(d2))、需深度合并嵌套结构(|只处理一层)、性能敏感且反复合并大字典(|每次都新建对象) - 相比
{**d1, **d2}:|更易读、支持方法链(如d1 | d2 | d3),且在右操作数不是 dict 时错误信息更明确
| 运算符在实际函数中怎么简化逻辑
典型场景是封装带默认参数的函数,避免手动写一堆 if key not in d: d[key] = default 或冗长的 dict(defaults, **override)(后者不兼容非字符串键)。
def make_request(url, params=None):
defaults = {'timeout': 30, 'retries': 3}
actual_params = defaults | (params or {})
# 后续只用 actual_params,逻辑干净
-
params or {}是必须的:因为None | {}会抛TypeError - 如果
params可能含非字典类型(如str),得先校验:isinstance(params, dict) - 注意:
|不递归合并嵌套字典。例如{'x': {'a': 1}} | {'x': {'b': 2}}结果是{'x': {'b': 2}}—— 整个值被替换,不是{'x': {'a': 1, 'b': 2}}
容易忽略的兼容性与调试陷阱
这个运算符只在 Python 3.9+ 有效,3.8 及以下会直接语法错误:SyntaxError: invalid syntax。CI 或多版本环境中尤其要小心。
- IDE(如旧版 PyCharm)可能标红或无法补全
|,不是代码错,是解释器版本识别问题 - 动态构建字典时不能用:
eval("d1 | d2")在 3.9+ 里可行,但可读性和安全性极差,不推荐 - 和
+=混淆:没有d1 |= d2的就地版本(那是 Python 3.9 的另一个特性 ——dict的__ior__,但需显式启用,且不如update()直观)
最常被跳过的点是:它不解决嵌套合并,也不处理 None 安全,这两处一旦出问题,错误表现往往在下游逻辑里,很难溯源到合并这一步。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











