| 运算符更适合配置合并:它实现不可变浅层合并,d1 | d2 返回新字典而不修改原字典,避免 update() 的就地修改和返回 None 问题,支持清晰链式调用如 default | env_cfg | cli_args。

因为 | 运算符在语义、性能和可维护性上更贴合配置合并的典型需求:不可变、右覆盖、链式清晰,但前提是你要的只是浅层合并。
配置合并最需要的是“不改原字典”
配置常被多处复用(比如 base_cfg 被 dev/staging/prod 多次叠加),一旦被 update() 就地修改,后续加载会出错。
-
d1 | d2总是返回新字典,d1和d2原封不动 —— 适合函数式风格或并发场景 -
d1.update(d2)返回None,且d1内容已变,若误写成cfg = cfg.update(override),cfg就变成None - 配置加载函数里直接返回
default | env_cfg | cli_args,无需临时变量,也无副作用
链式合并天然匹配配置优先级逻辑
配置通常有明确覆盖顺序:默认值 | 写出来就是线性优先级流,不用括号也能保证左结合。
-
base | staging | overrides—— 清晰表达“overrides 最终生效” - 别写
base | staging | prod | local:长链难调试,哪一层覆盖了timeout?得逐个 print;建议拆成带名变量:cfg = base | env_cfg | cli_args -
{**base, **staging, **overrides}功能等价,但视觉上不如|直观,尤其嵌套多层时容易漏掉**
但嵌套字典会被整体替换,不是递归合并
这是配置场景下最容易翻车的地方:你以为 {"db": {"host": "a"}} | {"db": {"port": 5433}} 会得到 {"db": {"host": "a", "port": 5433}},实际结果是 {"db": {"port": 5433}} —— "host" 丢了。
立即学习“Python免费学习笔记(深入)”;
-
|只做一层键值对覆盖,遇到同名 key,直接用右边整个 value 替换左边,不管 value 是 dict、list 还是 None - 真要合并嵌套结构(比如
database.url和database.pool_size分别来自不同来源),必须用deepmerge或手写递归函数 - 如果配置里大量使用嵌套(如 YAML 解析后),别指望
|自动处理,它压根不进第二层
类型安全得自己兜底,| 不做任何转换
环境变量读出来的 os.environ.get("DEBUG") 是字符串 "True",但配置期望布尔值;| 会原样塞进去,运行时才暴露问题。
- 别直接
env_dict | config_dict,先做类型清洗:{"debug": parse_bool(os.getenv("DEBUG"))} -
|对 key 类型无限制(支持int、tuple键),但如果你的配置系统只认字符串 key,就得额外校验 - 缺失关键键(如
"timeout")不会报错,而是静默继承左侧值 —— 这有时是优点,有时是隐患,靠测试覆盖比靠语法糖更可靠
真正麻烦的不是语法选哪个,而是配置来源混杂 + 类型不一致 + 浅合并需求共存时,| 的简洁性反而会掩盖逻辑复杂度。它很高效,但不智能。


















