page contents

Python字典合并:从 | 运算符到深拷贝的坑

去年双十一,促销配置合并出错,优惠券全场通用。老板在群里@我,我汗流浃背地回滚。根因?两个字典用update()合并,第二个字典的默认值把第一个覆盖了。看似简单,暗藏杀机。

attachments-2026-05-tNCXeKyv69fa9fc6a1be1.png去年双十一,促销配置合并出错,优惠券全场通用。老板在群里@我,我汗流浃背地回滚。根因?两个字典用update()合并,第二个字典的默认值把第一个覆盖了。看似简单,暗藏杀机。

update():老派但危险

dict1.update(dict2)是最常见的合并方式。它是原地修改,返回None。很多新手写成new = dict1.update(dict2),结果new是None,dict1却被改了。这种静默污染,Debug的时候能让你当场崩溃

base = {'a'1'b'2}
extra = {'b'99'c'3}

# 错误的写法!
merged = base.update(extra)
print(merged)   # None ☕
print(base)     # {'a': 1, 'b': 99, 'c': 3}  被污染了!

| 运算符:Python3.9的语法糖

Python3.9引入了|合并运算符。dict1 | dict2返回新字典,不污染原数据。看起来很美好?别急着欢呼。它只支持dict | dict**解包那种混合玩法不行。而且,也是浅拷贝

# 干净的新字典
merged = base | extra
print(base)  # {'a': 1, 'b': 2}  原数据安然无恙

# 链式合并
config = defaults | env_vars | cli_args

深拷贝:那个80%人踩过的坑

|update()都是浅拷贝。字典的值如果是列表或字典,合并后两边共享同一个引用。改一个,另一个也变。我同事因为这个bug把用户权限表搞串了,差点被祭天

defaults = {'users': ['alice']}
config = defaults | {'users': defaults['users']}
config['users'].append('bob')
print(defaults['users'])  # ['alice', 'bob']  淦!

深拷贝的正确打开方式

copy.deepcopy才是保命之道。但别无脑全量深拷贝,性能吃不起。我的做法是先浅拷贝,再对可疑的值按需深拷。精细化手术,不要大炮打蚊子。

import copy

def safe_merge(d1, d2):
    result = copy.deepcopy(d1)
    for k, v in d2.items():
        if isinstance(v, dict) and k in result:
            result[k] = safe_merge(result[k], v)
        else:
            result[k] = copy.deepcopy(v)
    return result

ChainMap:被忽略的轻量级方案

collections.ChainMap不合并,而是把多个字典链起来查询。查的时候按顺序找,找到就停。写多层级配置的时候超好用。而且它是零拷贝,性能爆炸。缺点是写操作只影响第一个字典。

from collections import ChainMap

defaults = {'theme''dark''lang''en'}
user_prefs = {'lang''zh'}
cli_args = {'debug'True}

config = ChainMap(cli_args, user_prefs, defaults)
print(config['lang'])   # 'zh'  按优先级命中
print(config['theme'])  # 'dark'   fallback到defaults

合并策略对比表

选哪种方案?看场景。我整理了一张表,面试的时候背下来能装个大的

方案          是否新对象    深/浅拷贝    多字典链式    适用场景
update()      否(原地)     浅拷贝       否           临时补丁
| 运算符      是           浅拷贝       是           配置合并
dict(**a,**b) 是           浅拷贝       否           简单合并
ChainMap      否(视图)     引用         是           优先级查询
deepcopy      是           深拷贝       手动实现      数据隔离

说的得罪人的

看到有人用{**d1, **d2}就觉得自己很Pythonic。大哥,键冲突的时候后面覆盖前面,你确定这是你想要的行为?生产环境里,显式永远比隐式安全。别为了少写几个字符,埋个雷给后人踩


递归合并:处理嵌套字典

配置文件经常是嵌套结构。json文件加载出来,两层三层嵌套很正常。|update()碰到嵌套直接覆盖外层,不会智能合并内层。这时候必须手写递归合并。


import copy

def deep_merge(base, override):
    result = copy.deepcopy(base)
    for k, v in override.items():
        if k in result and isinstance(result[k], dict) and isinstance(v, dict):
            result[k] = deep_merge(result[k], v)
        else:
            result[k] = copy.deepcopy(v)
    return result

base = {'db': {'host''localhost''port'3306}}
override = {'db': {'port'5432}}
print(deep_merge(base, override))
# {'db': {'host': 'localhost', 'port': 5432}}

更多相关技术内容咨询欢迎前往并持续关注好学星城论坛了解详情。

想高效系统的学习Python编程语言,推荐大家关注一个微信公众号:Python编程学习圈。每天分享行业资讯、技术干货供大家阅读,关注即可免费领取整套Python入门到进阶的学习资料以及教程,感兴趣的小伙伴赶紧行动起来吧。

attachments-2022-05-rLS4AIF8628ee5f3b7e12.jpg


  • 发表于 2026-05-06 09:59
  • 阅读 ( 41 )
  • 分类:Python开发

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

2059 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 2228 文章
  3. Pack 2059 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章