更新深度不一的嵌套字典的值

297 投票
31 回答
198671 浏览
提问于 2025-04-16 01:11

我想找一种方法,把字典 dictionary1 的内容更新为字典 update 的内容,但不想覆盖掉 levelA 的部分。

dictionary1 = {
    "level1": {
        "level2": {"levelA": 0, "levelB": 1}
    }
}
update = {
    "level1": {
        "level2": {"levelB": 10}
    }
}
dictionary1.update(update)
print(dictionary1)
{
    "level1": {
        "level2": {"levelB": 10}
    }
}

我知道,使用更新操作会删除 level2 的值,因为它是在更新最底层的 level1

考虑到 dictionary1update 的长度可能不一样,我该怎么处理这个问题呢?

31 个回答

37

我花了一些时间才搞明白这个问题,不过多亏了@Alex的帖子,他帮我填补了我缺失的部分。不过,我遇到了一个问题,就是如果递归的dict里面有一个值是list的话,所以我想分享一下,并扩展一下他的回答。

import collections

def update(orig_dict, new_dict):
    for key, val in new_dict.iteritems():
        if isinstance(val, collections.Mapping):
            tmp = update(orig_dict.get(key, { }), val)
            orig_dict[key] = tmp
        elif isinstance(val, list):
            orig_dict[key] = (orig_dict.get(key, []) + val)
        else:
            orig_dict[key] = new_dict[key]
    return orig_dict
63

如果你正在使用 pydantic 这个库(顺便说一下,这个库非常棒),你可以使用它的一些实用方法:

from pydantic.utils import deep_update

dictionary1 = deep_update(dictionary1, update)

更新:这里有一个 代码参考,这是 @Jorgu 提到的。如果你不想安装 pydantic,这段代码足够短,可以直接复制,只要确保遵循相关的许可协议就可以了。

393

@FM的回答有个大致的思路是对的,也就是用递归的方法,但代码写得有点奇怪,而且至少有一个错误。我建议可以这样做:

Python 2:

import collections

def update(d, u):
    for k, v in u.iteritems():
        if isinstance(v, collections.Mapping):
            d[k] = update(d.get(k, {}), v)
        else:
            d[k] = v
    return d

Python 3:

import collections.abc

def update(d, u):
    for k, v in u.items():
        if isinstance(v, collections.abc.Mapping):
            d[k] = update(d.get(k, {}), v)
        else:
            d[k] = v
    return d

这个错误出现在“更新”中有一个 kv 的项,其中 v 是一个 dict(字典),而 k 在要更新的字典中原本并不是一个键。@FM的代码在处理这个更新时“跳过”了这一部分(因为它在一个空的新 dict 上执行这个操作,而这个新字典没有被保存或返回,等递归调用结束后就丢失了)。

我其他的改动比较小:当 .get 可以更快更简洁地完成同样的工作时,就没有必要使用 if/else 的结构,而 isinstance 最好应用于抽象基类(而不是具体的类),这样更通用。

撰写回答