在Python中深度合并字典的字典
我需要合并多个字典,这里有个例子:
dict1 = {1:{"a":{"A"}}, 2:{"b":{"B"}}}
dict2 = {2:{"c":{"C"}}, 3:{"d":{"D"}}}
在这个例子中,A
、B
、C
和D
是树的叶子节点,比如说{"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'}}
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
中。
注意:我修改了最初的回答,使第一个参数会被改变;这样“减少”的过程更容易解释。