在Python中深度合并字典的字典

226 投票
36 回答
147022 浏览
提问于 2025-04-17 00:24

我需要合并多个字典,这里有个例子:

dict1 = {1:{"a":{"A"}}, 2:{"b":{"B"}}}

dict2 = {2:{"c":{"C"}}, 3:{"d":{"D"}}}

在这个例子中,ABCD是树的叶子节点,比如说{"info1":"value", "info2":"value2"}

这些字典的层级(深度)是未知的,可能会是像{2:{"c":{"z":{"y":{C}}}}}这样的结构。

在我的例子里,它表示一个目录/文件结构,节点是文档,叶子节点是文件。

我想把它们合并成一个整体,得到:

 dict3 = {1:{"a":{"A"}}, 2:{"b":{"B"},"c":{"C"}}, 3:{"d":{"D"}}}

我不太确定怎么用Python简单地做到这一点。

36 个回答

52

这里有一个简单的方法可以使用生成器来实现:

def mergedicts(dict1, dict2):
    for k in set(dict1.keys()).union(dict2.keys()):
        if k in dict1 and k in dict2:
            if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
                yield (k, dict(mergedicts(dict1[k], dict2[k])))
            else:
                # If one of the values is not a dict, you can't continue merging it.
                # Value from second dict overrides one in first and we move on.
                yield (k, dict2[k])
                # Alternatively, replace this with exception raiser to alert you of value conflicts
        elif k in dict1:
            yield (k, dict1[k])
        else:
            yield (k, dict2[k])

dict1 = {1:{"a":"A"},2:{"b":"B"}}
dict2 = {2:{"c":"C"},3:{"d":"D"}}

print dict(mergedicts(dict1,dict2))

这段代码会输出:

{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}
62

你可以试试 mergedeep


安装方法

$ pip3 install mergedeep

使用方法

from mergedeep import merge

a = {"keyA": 1}
b = {"keyB": {"sub1": 10}}
c = {"keyB": {"sub2": 20}}

merge(a, b, c) 

print(a)
# {"keyA": 1, "keyB": {"sub1": 10, "sub2": 20}}

想要查看所有选项,可以去看看 文档

216

这其实挺复杂的,特别是当你想在数据不一致时得到一个有用的错误信息,同时又要正确处理重复但一致的条目(这里没有其他答案能做到这一点)。

假设你的条目数量不多,使用递归函数是最简单的办法:

def merge(a: dict, b: dict, path=[]):
    for key in b:
        if key in a:
            if isinstance(a[key], dict) and isinstance(b[key], dict):
                merge(a[key], b[key], path + [str(key)])
            elif a[key] != b[key]:
                raise Exception('Conflict at ' + '.'.join(path + [str(key)]))
        else:
            a[key] = b[key]
    return a

# works
print(merge({1:{"a":"A"},2:{"b":"B"}}, {2:{"c":"C"},3:{"d":"D"}}))
# has conflict
merge({1:{"a":"A"},2:{"b":"B"}}, {1:{"a":"A"},2:{"b":"C"}})

注意,这个方法会改变 a 的内容——b 的内容会被添加到 a 中(而且 a 也会被返回)。如果你想保留原来的 a,可以这样调用它:merge(dict(a), b)

agf 在下面提到,你可能有不止两个字典,这种情况下你可以使用:

from functools import reduce
reduce(merge, [dict1, dict2, dict3...])

这样所有的内容都会被添加到 dict1 中。

注意:我修改了最初的回答,使第一个参数会被改变;这样“减少”的过程更容易解释。

撰写回答