我需要合并多个字典,这是我有例如:
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轻松做到这一点。
这实际上是相当棘手的-特别是如果你想要一个有用的错误消息时,事情是不一致的,同时正确地接受重复但一致的条目(这是这里没有其他答案做的..)。
假设你没有大量的条目,递归函数是最简单的:
from functools import reduce
def merge(a, b, path=None):
"merges b into a"
if path is None: 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]:
pass # same leaf value
else:
raise Exception('Conflict at %s' % '.'.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指出(下面),你可能有两个以上的字典,在这种情况下,你可以使用:
reduce(merge, [dict1, dict2, dict3...])
所有内容都将被添加到dict1中。
注意:我编辑了我的初始答案以改变第一个参数;这使得“reduce”更容易解释
这里我有另一个稍微不同的解决方案:
def deepMerge(d1, d2, inconflict = lambda v1,v2 : v2) :
''' merge d2 into d1. using inconflict function to resolve the leaf conflicts '''
for k in d2:
if k in d1 :
if isinstance(d1[k], dict) and isinstance(d2[k], dict) :
deepMerge(d1[k], d2[k], inconflict)
elif d1[k] != d2[k] :
d1[k] = inconflict(d1[k], d2[k])
else :
d1[k] = d2[k]
return d1
默认情况下,它解决冲突,支持来自第二个字典的值,但您可以很容易地覆盖这一点,使用一些巫术,您甚至可以抛出异常。:)。
我有两个字典(a和b),每个字典可以包含任意数量的嵌套字典。我想递归地合并它们,b优先于a。
将嵌套字典视为树,我想要的是:
更新a,使b中每个叶结点的每条路径都表示在a中
如果在b的对应路径中找到了叶子,则覆盖a的子树
保持所有b个叶节点都是叶节点的不变式。
现有的答案对我来说有点复杂,有些细节被束之高阁。我将以下内容整合在一起,它们通过了我的数据集的单元测试。
def merge_map(a, b):
if not isinstance(a, dict) or not isinstance(b, dict):
return b
for key in b.keys():
a[key] = merge_map(a[key], b[key]) if key in a else b[key]
return a
示例(为清晰起见,已格式化):
a = {
1 : {'a': 'red',
'b': {'blue': 'fish', 'yellow': 'bear' },
'c': { 'orange': 'dog'},
},
2 : {'d': 'green'},
3: 'e'
}
b = {
1 : {'b': 'white'},
2 : {'d': 'black'},
3: 'e'
}
>>> merge_map(a, b)
{1: {'a': 'red',
'b': 'white',
'c': {'orange': 'dog'},},
2: {'d': 'black'},
3: 'e'}
b中需要维护的路径为:
1 -> 'b' -> 'white'
2 -> 'd' -> 'black'
3 -> 'e'。
A拥有独特且不冲突的路径:
1 -> 'a' -> 'red'
1 -> 'c' -> 'orange' -> 'dog'
所以它们仍然在合并后的映射中表示。
由于dictviews支持集合操作,我能够极大地简化jterrace的答案。
def merge(dict1, dict2):
for k in dict1.keys() - dict2.keys():
yield (k, dict1[k])
for k in dict2.keys() - dict1.keys():
yield (k, dict2[k])
for k in dict1.keys() & dict2.keys():
yield (k, dict(merge(dict1[k], dict2[k])))
任何将dict与非dict(技术上讲,一个带有'keys'方法的对象和一个没有'keys'方法的对象)组合的尝试都会引发AttributeError。这包括对函数的初始调用和递归调用。这正是我想要的,所以我离开了。您可以很容易地捕获递归调用抛出的AttributeErrors,然后生成您想要的任何值。
def m(a,b):
aa = {
k : dict(a.get(k,{}), **v) for k,v in b.items()
}
aap = print(aa)
return aap
d1 = {1:{"a":"A"}, 2:{"b":"B"}}
d2 = {2:{"c":"C"}, 3:{"d":"D"}}
dict1 = {1:{"a":{1}}, 2:{"b":{2}}}
dict2 = {2:{"c":{222}}, 3:{"d":{3}}}
m(d1,d2)
m(dict1,dict2)
"""
Output :
{2: {'b': 'B', 'c': 'C'}, 3: {'d': 'D'}}
{2: {'b': {2}, 'c': {222}}, 3: {'d': {3}}}
"""