字典合并|运算符仅做浅层键值叠加,不递归处理嵌套结构,右侧值直接覆盖左侧同键值(如{'x': {'a':1}} | {'x': {'b':2}}得{'x': {'b':2}}),且不深拷贝,嵌套可变对象仍共享引用。

字典合并 | 运算符不触发深拷贝,只做浅层键值叠加
它根本不会递归复制嵌套结构,所有值都按引用直接搬过去。比如 {'x': {'a': 1}} | {'x': {'b': 2}} 的结果是 {'x': {'b': 2}},不是 {'x': {'a': 1, 'b': 2}}——右侧整个值对象替换了左侧的,连浅拷贝都不做。
常见错误现象:
- 你本想“合并配置”,却意外丢失了左字典里某个嵌套字典的全部字段
- 修改合并后字典中某嵌套字典的值,发现原始字典也被改了(因为引用同一对象)
使用场景:适合扁平配置拼接(如 API 参数、环境变量),不适合处理含可变嵌套结构的数据。
空字典参与 | 运算时实际发生的是浅拷贝
{} | d 等价于 d.copy(),不是 dict(d) 或 copy.deepcopy(d)。这意味着如果 d 的某个值是列表或嵌套字典,新字典里依然指向同一个对象。
容易踩的坑:
- 误以为
{}|d是“安全隔离”,结果后续修改嵌套值污染了原d - 在函数返回值中用
defaults | user_input,但user_input里带['tag1', 'tag2'],调用方 later 修改这个列表,影响了其他地方缓存的配置副本
参数差异:该行为与 dict(d) 相同(仅限字符串键),但比 {**d} 更明确——后者在 Python 3.5+ 就存在,而 {} | d 是 3.9+ 才有且语义统一。
| 和 |= 都不解决可变默认值引发的共享引用问题
它们只是把键值对“贴”过去,不会检测值是否可变,更不会自动 .copy()。如果你写:
base = {'flags': []}
d1 = base | {'name': 'a'}
d2 = base | {'name': 'b'}
d1['flags'].append('x')
你会发现 d2['flags'] 也变成了 ['x'],因为 base['flags'] 是同一个列表对象,被两个结果字典共用。
正确做法:
- 初始化时就避免可变默认值:
base = {'flags': []}→ 改成base = {'flags': None},后续用时再.copy()或新建 - 需要独立副本,显式调用:
base | {'flags': base.get('flags', []).copy()} - 或者封装为函数,内部做浅拷贝:
lambda d: {k: v.copy() if isinstance(v, dict) else v for k, v in d.items()}
性能影响:每次手动 .copy() 增加开销,但比起意外的逻辑 bug,这点成本值得付。
Python 3.8 及以下版本无法降级兼容 | 运算符
这不是语法警告,而是硬性报错:SyntaxError: invalid syntax。CI 流水线、旧版 Docker 镜像、某些 IDE 的语法检查器(如 PyCharm 2021.3 之前)都会直接失败。
调试时注意:
- 别在报错行反复检查括号或冒号——问题在解释器版本
- 用
python --version确认运行时版本,不是开发机版本 - 若必须多版本兼容,用
{**d1, **d2}替代d1 | d2;用d1.update(d2)替代d1 |= d2,但要意识到前者不返回值、后者会修改原字典
真正容易被忽略的点:| 运算符的“不可变性”是假象——它只保证不改输入字典的键值映射关系,但对值对象本身完全放手。嵌套可变对象的生命周期和所有权,仍得靠人来管。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











