关于python:字典词典合并

Dictionaries of dictionaries merge

我需要合并多个字典,例如:

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

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

其中ABCD是树的叶子,就像{"info1":"value","info2":"value2"}一样。

字典的深度未知,可能是{2:{"c":{"z":{"y":{C}}}}}

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

我想合并它们以获得:

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

我不知道如何用Python轻松做到这一点。


这实际上是相当棘手的-特别是当事情不一致时,您需要一条有用的错误消息,同时正确地接受重复但一致的条目(这里没有其他答案可以做到…)

假设您没有大量的条目,递归函数是最简单的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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
<div class="
suo-content">[collapse title=""]<ul><li>然后,您可以将它插入到<wyn>reduce</wyn>或等效循环中,以使用任意数量的<wyn>dict</wyn>,而不是两个。然而,我也不确定这是否符合他想要的(他不清楚),你最终以<wyn>2: {'c': {'z': {'y': {'info1': 'value', 'info2': 'value2'}}}, 'b': {'info1': 'value', 'info2': 'value2'}}</wyn>作为他的第二个例子,我不确定他是否想要<wyn>z</wyn>和<wyn>y</wyn>变平?</li><li>它们是目录结构,所以我不认为S/HE想要任何扁平化的东西?哦,对不起,错过了"多字典"。是的,减量比较好。再加上一点。</li><li>这正是我想要的!对不起,我不够清楚…我觉得我对python没意见,似乎不是这样的:-/我需要一个递归函数,因为有嵌套的dict,这个函数可以工作,我可以理解它:)但是我似乎不能使它与reduce一起工作…</li><li>我不久前更新了代码,使它能更好地使用reduce。当前版本对我来说似乎没问题-你能发布一个错误吗?例如,这项工作:<wyn>print(reduce(merge, [{1:{"a":"A"},2:{"b":"B"}}, {2:{"c":"C"},3:{"d":"D"}}, {7:"foo"}]))</wyn>。</li><li>这可能是一个python2问题,我不确定:我确实在第5行有一个typeerror,none typeobject,因为a是none。命令是reduce(merge,[dict1,dict2,dict3,dict4])</li><li>是否定义了dict1?我的意思是,为了成为一个变量,你需要提供一些字典。请参见上面注释中的示例(即python2.7)。</li><li>是的,因为合并(dict1,dict2)工作得很好,打印dict1在函数后得到正确的合并dict。不管怎样,我只有4个基本的口述,所以我可以手工做。</li><li>你包括最后的"退货"吗?</li><li>好吧,我要躲到很远的地方。对不起!再次感谢!</li><li>嘿,在那儿。我在你的答案上发布了一些更新,以突出这种方法的缺陷。这不是一个真正的错误,但也许你可以更新你的答案,让人们知道这一点?stackoverflow.com/a/27584990/339505</li><li>太棒了,谢谢!</li><li>对于任何将列表作为dicts下最后一个嵌套级别的人,您可以这样做,而不是引发连接两个列表的错误:<wyn>a[key] = a[key] + b[key]</wyn>。谢谢你的帮助。</li><li>感谢您建议使用reduce!</li><li>@安德鲁·库克:如果这个合并操作需要在大量的记录上执行,比如数百万条记录,怎么办?那么,该采用什么方法呢?不是递归??</li><li>>如果您想保留a,可以像merge(dict(a),b)一样调用它,请注意嵌套的dict仍然会发生变化。要避免这种情况,请使用<wyn>copy.deepcopy</wyn>。</li><li>如果<wyn>a</wyn>中有一个键不在<wyn>b</wyn>中,那么这个键就失败了,发电机的答案会处理这个问题。</li></ul>[/collapse]</div><hr><P>下面是一个使用发电机的简单方法:</P>[cc lang="python"]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
<div class="suo-content">[collapse title=""]<ul><li>如果要保留生成器主题,可以链接(dict1.keys(),dict2.keys())</li><li>不会有重复的钥匙吗?</li><li>这一个似乎可以完成这项工作,至少在我的数据集上是这样,但是由于我从来没有很好地理解产量和生成器,我几乎迷失了为什么,但我会更努力一点,也许会有用!</li><li>啊,是的,它会有重复的钥匙。对不起,你还是需要把它包起来。</li><li>我发现这个特别有用。但最好是让函数作为参数来解决冲突。</li><li>我认为如果两个dict键相同,其中一个对应的值是dict,而另一个不是,就不起作用了。例如:dict1=1:a":"a""2:"b""b""dict2=2:"c",3:"d":"d""</li><li>多谢了,这个解决方案与大字典(<wyn>@andrew cook</wyn>的解决方案也很好,但需要您将所有的字典都载入内存)@kispaljr确实,这是为具有相同格式的字典设计的。</li><li>为了更加优雅,请删除yield圆括号,并将第一个for循环改为<wyn>for k in set(dict1) | set(dict2):</wyn></li></ul>[/collapse]</div><hr>
<p>
One issue with this question is that the values of the dict can be arbitrarily complex pieces of data. Based upon these and other answers I came up with this code:

</p>

[cc lang="python"]class YamlReaderError(Exception):
    pass

def data_merge(a, b):
   """merges b into a and return merged result

    NOTE: tuples and arbitrary objects are not handled as it is totally ambiguous what should happen"""

    key = None
    # ## debug output
    # sys.stderr.write("DEBUG: %s to %s
" %(b,a))
    try:
        if a is None or isinstance(a, str) or isinstance(a, unicode) or isinstance(a, int) or isinstance(a, long) or isinstance(a, float):
            # border case for first run or if a is a primitive
            a = b
        elif isinstance(a, list):
            # lists can be only appended
            if isinstance(b, list):
                # merge lists
                a.extend(b)
            else:
                # append to list
                a.append(b)
        elif isinstance(a, dict):
            # dicts must be merged
            if isinstance(b, dict):
                for key in b:
                    if key in a:
                        a[key] = data_merge(a[key], b[key])
                    else:
                        a[key] = b[key]
            else:
                raise YamlReaderError('Cannot merge non-dict"
%s" into dict"%s"' % (b, a))
        else:
            raise YamlReaderError('NOT IMPLEMENTED"
%s" into"%s"' % (b, a))
    except TypeError, e:
        raise YamlReaderError('TypeError"
%s" in key"%s" when merging"%s" into"%s"' % (e, key, b, a))
    return a

我的用例是合并yaml文件,在这里我只需要处理可能的数据类型的子集。因此,我可以忽略元组和其他对象。对我来说,合理的合并逻辑意味着

  • 更换标尺
  • 附加列表
  • 通过添加缺少的键和更新现有键来合并dict

其他一切和不可预见的事情都会导致错误。


Dictionaries of dictionaries merge

因为这是典型的问题(尽管有些非一般性的问题),所以我提供了解决这个问题的典型的Python方法。

最简单的例子:"叶是以空字典结尾的嵌套字典":

1
2
3
4
d1 = {'a': {1: {'foo': {}}, 2: {}}}
d2 = {'a': {1: {}, 2: {'bar': {}}}}
d3 = {'b': {3: {'baz': {}}}}
d4 = {'a': {1: {'quux': {}}}}

这是递归最简单的情况,我建议使用两种简单的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def rec_merge1(d1, d2):
    '''return new merged dict of dicts'''
    for k, v in d1.items(): # in Python 2, use .iteritems()!
        if k in d2:
            d2[k] = rec_merge1(v, d2[k])
    d3 = d1.copy()
    d3.update(d2)
    return d3

def rec_merge2(d1, d2):
    '''update first dict with second recursively'''
    for k, v in d1.items(): # in Python 2, use .iteritems()!
        if k in d2:
            d2[k] = rec_merge2(v, d2[k])
    d1.update(d2)
    return d1

我相信我更喜欢第二种,而不是第一种,但请记住,第一种的原始状态必须从它的起源重新构建。用法如下:

1
2
3
4
5
>>> from functools import reduce # only required for Python 3.
>>> reduce(rec_merge1, (d1, d2, d3, d4))
{'a': {1: {'quux': {}, 'foo': {}}, 2: {'bar': {}}}, 'b': {3: {'baz': {}}}}
>>> reduce(rec_merge2, (d1, d2, d3, d4))
{'a': {1: {'quux': {}, 'foo': {}}, 2: {'bar': {}}}, 'b': {3: {'baz': {}}}}

。复杂情况:"叶是任何其他类型的:

所以,如果它们以听写结尾,那么合并结尾空听写就是一个简单的例子。如果不是的话,那也不是那么微不足道。如果是字符串,如何合并它们?集合也可以类似地更新,所以我们可以进行这种处理,但是我们会丢失它们合并的顺序。那么秩序有关系吗?

因此,代替更多信息,最简单的方法是,如果两个值都不是dict,则为它们提供标准的更新处理:即,第二个dict的值将覆盖第一个值,即使第二个dict的值为none,而第一个值是包含大量信息的dict。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
d1 = {'a': {1: 'foo', 2: None}}
d2 = {'a': {1: None, 2: 'bar'}}
d3 = {'b': {3: 'baz'}}
d4 = {'a': {1: 'quux'}}

from collections import MutableMapping

def rec_merge(d1, d2):
    '''
    Update two dicts of dicts recursively,
    if either mapping has leaves that are non-dicts,
    the second's leaf overwrites the first's.
    '''

    for k, v in d1.items(): # in Python 2, use .iteritems()!
        if k in d2:
            # this next check is the only difference!
            if all(isinstance(e, MutableMapping) for e in (v, d2[k])):
                d2[k] = rec_merge(v, d2[k])
            # we could further check types and merge as appropriate here.
    d3 = d1.copy()
    d3.update(d2)
    return d3

现在

1
2
from functools import reduce
reduce(rec_merge, (d1, d2, d3, d4))

退货

1
{'a': {1: 'quux', 2: 'bar'}, 'b': {3: 'baz'}}

。对原问题的适用:

我必须删除字母周围的大括号,并将它们放在单引号中,这样才能成为合法的python(否则它们将在python 2.7+中设置文本),并附加一个缺少的大括号:

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

rec_merge(dict1, dict2)现在返回:

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

与原始问题的预期结果相匹配(更改后,如{A}'A')。


Based on @andrew cooke. This version handles nested lists of dicts and also allows the option to update the values

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def merge(a, b, path=None, update=True):
   "http://stackoverflow.com/questions/7204805/python-dictionaries-of-dictionaries-merge"
   "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
            elif isinstance(a[key], list) and isinstance(b[key], list):
                for idx, val in enumerate(b[key]):
                    a[key][idx] = merge(a[key][idx], b[key][idx], path + [str(key), str(idx)], update=update)
            elif update:
                a[key] = b[key]
            else:
                raise Exception('Conflict at %s' % '.'.join(path + [str(key)]))
        else:
            a[key] = b[key]
    return a


If you have an unknown level of dictionaries, then I would suggest a recursive function:

1
2
3
4
5
6
7
8
9
10
11
def combineDicts(dictionary1, dictionary2):
    output = {}
    for item, value in dictionary1.iteritems():
        if dictionary2.has_key(item):
            if isinstance(dictionary2[item], dict):
                output[item] = combineDicts(value, dictionary2.pop(item))
        else:
            output[item] = value
    for item, value in dictionary2.iteritems():
         output[item] = value
    return output


Based on answers from @andrew cooke.
It takes care of nested lists in a better way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def deep_merge_lists(original, incoming):
   """
    Deep merge two lists. Modifies original.
    Reursively call deep merge on each correlated element of list.
    If item type in both elements are
     a. dict: call deep_merge_dicts on both values.
     b. list: Calls deep_merge_lists on both values.
     c. any other type: Value is overridden.
     d. conflicting types: Value is overridden.

    If length of incoming list is more that of original then extra values are appended.
   """

    common_length = min(len(original), len(incoming))
    for idx in range(common_length):
        if isinstance(original[idx], dict) and isinstance(incoming[idx], dict):
            deep_merge_dicts(original[idx], incoming[idx])

        elif isinstance(original[idx], list) and isinstance(incoming[idx], list):
            deep_merge_lists(original[idx], incoming[idx])

        else:
            orginal[idx] = incoming[idx]

    for idx in range(common_length, len(incoming)):
        original.append(incoming[idx])


def deep_merge_dicts(original, incoming):
   """
    Deep merge two dictionaries. Modfies original.
    For key conflicts if both values are:
     a. dict: Recursivley call deep_merge_dicts on both values.
     b. list: Calls deep_merge_lists on both values.
     c. any other type: Value is overridden.
     d. conflicting types: Value is overridden.

   """

    for key in incoming:
        if key in original:
            if isinstance(original[key], dict) and isinstance(incoming[key], dict):
                deep_merge_dicts(original[key], incoming[key])

            elif isinstance(original[key], list) and isinstance(incoming[key], list):
                deep_merge_lists(original[key], incoming[key])

            else:
                original[key] = incoming[key]
        else:
            original[key] = incoming[key]


这个简单的递归过程将把一个字典合并到另一个字典中,同时覆盖冲突的键:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/usr/bin/env python2.7

def merge_dicts(dict1, dict2):
   """ Recursively merges dict2 into dict1"""
    if not isinstance(dict1, dict) or not isinstance(dict2, dict):
        return dict2
    for k in dict2:
        if k in dict1:
            dict1[k] = merge_dicts(dict1[k], dict2[k])
        else:
            dict1[k] = dict2[k]
    return dict1

print (merge_dicts({1:{"a":"A
<hr><P>这个版本的函数将解释n个字典,并且只有字典——不能传递不正确的参数,否则会导致类型错误。合并本身会导致键冲突,它不会在合并链的另一端覆盖字典中的数据,而是创建一组值并附加到该值;不会丢失任何数据。</P><P>它可能不是页面上最有效的,但它是最彻底的,当你将2到n个听写合并时,你不会丢失任何信息。</P>[cc lang="
python"]def merge_dicts(*dicts):
    if not reduce(lambda x, y: isinstance(y, dict) and x, dicts, True):
        raise TypeError,"
Object in *dicts not of type dict"
    if len(dicts) < 2:
        raise ValueError,"
Requires 2 or more dict objects"


    def merge(a, b):
        for d in set(a.keys()).union(b.keys()):
            if d in a and d in b:
                if type(a[d]) == type(b[d]):
                    if not isinstance(a[d], dict):
                        ret = list({a[d], b[d]})
                        if len(ret) == 1: ret = ret[0]
                        yield (d, sorted(ret))
                    else:
                        yield (d, dict(merge(a[d], b[d])))
                else:
                    raise TypeError,"
Conflicting key:value type assignment"
            elif d in a:
                yield (d, a[d])
            elif d in b:
                yield (d, b[d])
            else:
                raise KeyError

    return reduce(lambda x, y: dict(merge(x, y)), dicts[1:], dicts[0])

print merge_dicts({1:1,2:{1:2}},{1:2,2:{3:1}},{4:4})

输出:1:[1,2],2:1:2,3:1,4:4


因为dictview支持set操作,所以我可以大大简化jterrace的答案。

1
2
3
4
5
6
7
8
9
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,然后生成您希望的任何值。


安德鲁·库克的回答有一个小问题:在某些情况下,当你修改返回的dict时,它会修改第二个参数b。具体来说,这是因为这一行:

1
2
3
4
if key in a:
    ...
else:
    a[key] = b[key]

如果b[key]dict的话,它将简单地分配给a,这意味着对该dict的任何后续修改将同时影响ab

1
2
3
4
5
6
7
a={}
b={'1':{'2':'b'}}
c={'1':{'3':'c'}}
merge(merge(a,b), c) # {'1': {'3': 'c', '2': 'b'}}
a # {'1': {'3': 'c', '2': 'b'}} (as expected)
b # {'1': {'3': 'c', '2': 'b'}} <----
c # {'1': {'3': 'c'}} (unmodified)

要解决此问题,必须用以下内容替换行:

1
2
3
4
if isinstance(b[key], dict):
    a[key] = clone_dict(b[key])
else:
    a[key] = b[key]

其中,clone_dict为:

1
2
3
4
5
6
7
8
def clone_dict(obj):
    clone = {}
    for key, value in obj.iteritems():
        if isinstance(value, dict):
            clone[key] = clone_dict(value)
        else:
            clone[key] = value
    return

仍然如此。这显然不能解释listset和其他东西,但我希望它能说明合并dicts时的陷阱。

为了完整起见,这里是我的版本,您可以通过它传递多个dicts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def merge_dicts(*args):
    def clone_dict(obj):
        clone = {}
        for key, value in obj.iteritems():
            if isinstance(value, dict):
                clone[key] = clone_dict(value)
            else:
                clone[key] = value
        return

    def merge(a, b, 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
                else:
                    raise Exception('Conflict at `{path}\''.format(path='.'.join(path + [str(key)])))
            else:
                if isinstance(b[key], dict):
                    a[key] = clone_dict(b[key])
                else:
                    a[key] = b[key]
        return a
    return reduce(merge, args, {})


概述

以下方法将口述的深度合并问题细分为:

  • 参数化浅合并函数merge(f)(a,b)使用函数f合并两个dict ab

  • merge一起使用的递归合并函数f

  • 实施

    合并两个(非嵌套)dict的函数可以用很多方法编写。我个人喜欢

    1
    2
    3
    4
    5
    def merge(f):
        def merge(a,b):
            keys = a.keys() | b.keys()
            return {key:f(*[a.get(key), b.get(key)]) for key in keys}
        return merge

    定义适当的递归合并函数f的一个好方法是使用multipledispatch,它允许定义根据参数类型沿着不同路径进行计算的函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from multipledispatch import dispatch

    #for anything that is not a dict return
    @dispatch(object, object)
    def f(a, b):
        return b if b is not None else a

    #for dicts recurse
    @dispatch(dict, dict)
    def f(a,b):
        return merge(f)(a,b)

    例子

    要合并两个嵌套的dict,只需使用merge(f),例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    dict1 = {1:{"a":"A
    <hr><P>短N-甜:</P>[cc lang="
    python"]from collections.abc import MutableMapping as Map

    def nested_update(d, v):
    "
    ""
    Nested update of dict-like 'd' with dict-like 'v'.
    """

    for key in v:
        if key in d and isinstance(d[key], Map) and isinstance(v[key], Map):
            nested_update(d[key], v[key])
        else:
            d[key] = v[key]

    这类似于(并基于)Python的dict.update方法。它返回None(如果您愿意,可以随时添加return d),因为它更新了dict dv中的键将覆盖d中的任何现有键(它不尝试解释dict的内容)。

    它还可以用于其他("dict-like")映射。


    这有助于将dict2中的所有项目合并到dict1中:

    1
    2
    3
    4
    5
    6
    for item in dict2:
        if item in dict1:
            for leaf in dict2[item]:
                dict1[item][leaf] = dict2[item][leaf]
        else:
            dict1[item] = dict2[item]

    请测试一下,告诉我们这是否是你想要的。

    编辑:

    上述解决方案只合并一个级别,但正确地解决了op给出的示例。要合并多个级别,应使用递归。


    我有两个字典(ab,每个字典可以包含任意数量的嵌套字典。我想递归地合并它们,b优先于a

    考虑到嵌套字典是树,我想要的是:

    • 更新a以便b中每个叶的路径都在a中表示。
    • 覆盖a的子树,如果在b的相应路径中找到一个叶子。
      • 保持所有b叶节点保持叶不变。

    现有的答案对我的口味来说有点复杂,并在货架上留下了一些细节。我拼凑了以下内容,这些内容通过了我的数据集的单元测试。

    1
    2
    3
    4
    5
    6
    7
      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

    示例(格式清晰):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     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中的路径是:

    • 江户十一〔11〕。
    • 埃多克斯1〔12〕
    • 埃多克斯1〔13〕。

    a具有独特且无冲突的路径:

    • 埃多克斯1〔15〕
    • 江户十一〔16〕号

    所以它们仍然在合并的映射中表示。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    class Utils(object):

       """

        >>> a = { 'first' : { 'all_rows' : { 'pass' : 'dog', 'number' : '1' } } }
        >>> b = { 'first' : { 'all_rows' : { 'fail' : 'cat', 'number' : '5' } } }
        >>> Utils.merge_dict(b, a) == { 'first' : { 'all_rows' : { 'pass' : 'dog', 'fail' : 'cat', 'number' : '5' } } }
        True

        >>> main = {'a': {'b': {'test': 'bug'}, 'c': 'C'}}
        >>> suply = {'a': {'b': 2, 'd': 'D', 'c': {'test': 'bug2'}}}
        >>> Utils.merge_dict(main, suply) == {'a': {'b': {'test': 'bug'}, 'c': 'C', 'd': 'D'}}
        True

       """


        @staticmethod
        def merge_dict(main, suply):
           """
            获取融合的字典,以main为主,suply补充,冲突时以main为准
            :return:
           """

            for key, value in suply.items():
                if key in main:
                    if isinstance(main[key], dict):
                        if isinstance(value, dict):
                            Utils.merge_dict(main[key], value)
                        else:
                            pass
                    else:
                        pass
                else:
                    main[key] = value
            return main

    if __name__ == '__main__':
        import doctest
        doctest.testmod()


    如果有人想用另一种方法来解决这个问题,这里是我的解决方案。

    优点:简短、声明性和功能性风格(递归,没有突变)。

    潜在的缺点:这可能不是你想要的合并。请查阅docstring以了解语义。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    def deep_merge(a, b):
       """
        Merge two values, with `b` taking precedence over `a`.

        Semantics:
        - If either `a` or `b` is not a dictionary, `a` will be returned only if
          `b` is `None`. Otherwise `b` will be returned.
        - If both values are dictionaries, they are merged as follows:
            * Each key that is found only in `a` or only in `b` will be included in
              the output collection with its value intact.
            * For any key in common between `a` and `b`, the corresponding values
              will be merged with the same semantics.
       """

        if not isinstance(a, dict) or not isinstance(b, dict):
            return a if b is None else b
        else:
            # If we're here, both a and b must be dictionaries or subtypes thereof.

            # Compute set of all keys in both dictionaries.
            keys = set(a.keys()) | set(b.keys())

            # Build output dictionary, merging recursively values with common keys,
            # where `None` is used to mean the absence of a value.
            return {
                key: deep_merge(a.get(key), b.get(key))
                for key in keys
            }

    我一直在测试您的解决方案,并决定在我的项目中使用它:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    def mergedicts(dict1, dict2, conflict, no_conflict):
        for k in set(dict1.keys()).union(dict2.keys()):
            if k in dict1 and k in dict2:
                yield (k, conflict(dict1[k], dict2[k]))
            elif k in dict1:
                yield (k, no_conflict(dict1[k]))
            else:
                yield (k, no_conflict(dict2[k]))

    dict1 = {1:{"a":"A
    <hr>
    <p>
    The code will depend on your rules for resolving merge conflicts, of course. Here's a version which can take an arbitrary number of arguments and merges them recursively to an arbitrary depth, without using any object mutation. It uses the following rules to resolve merge conflicts:
    </p>


    <ul>


    <li>
    dictionaries take precedence over non-dict values (<wyn>{"
    foo": {...}}</wyn> takes precedence over <wyn>{"foo":"bar"}</wyn>)
    </li>


    <li>
    later arguments take precedence over earlier arguments (if you merge <wyn>{"
    a": 1}</wyn>, <wyn>{"a", 2}</wyn>, and <wyn>{"a": 3}</wyn> in order, the result will be <wyn>{"a": 3}</wyn>)
    </li>


    </ul>


    [cc lang="
    python"]try:
        from collections import Mapping
    except ImportError:
        Mapping = dict

    def merge_dicts(*dicts):                                                            
       "
    ""                                                                            
        Return a new dictionary that is the result of merging the arguments together.  
        In case of conflicts, later arguments take precedence over earlier arguments.  
       """                                                                            
        updated = {}                                                                    
        # grab all keys                                                                
        keys = set()                                                                    
        for d in dicts:                                                                
            keys = keys.union(set(d))                                                  

        for key in keys:                                                                
            values = [d[key] for d in dicts if key in d]                                
            # which ones are mapping types? (aka dict)                                  
            maps = [value for value in values if isinstance(value, Mapping)]            
            if maps:                                                                    
                # if we have any mapping types, call recursively to merge them          
                updated[key] = merge_dicts(*maps)                                      
            else:                                                                      
                # otherwise, just grab the last value we have, since later arguments    
                # take precedence over earlier arguments                                
                updated[key] = values[-1]                                              
        return updated


    我这里有另一个稍微不同的解决方案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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

    默认情况下,它解决了冲突,有利于第二个dict中的值,但是您可以轻松地覆盖它,通过一些巫术,您甚至可以从中抛出异常。:)


    嘿,我也有同样的问题,但我想有一个解决方案,我会把它贴在这里,以防它对其他人也有用,基本上是合并嵌套字典并添加值,对我来说,我需要计算一些概率,所以这一个非常有效:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #used to copy a nested dict to a nested dict
    def deepupdate(target, src):
        for k, v in src.items():
            if k in target:
                for k2, v2 in src[k].items():
                    if k2 in target[k]:
                        target[k][k2]+=v2
                    else:
                        target[k][k2] = v2
            else:
                target[k] = copy.deepcopy(v)

    通过使用上述方法,我们可以合并:

    目标='6,6':'6,63':1,'63,4':'4,4':1,'4,4':'4,3':1,'6,63':'63,4':1

    src='5,4':'4,4':1,'5,5':'5,4':1,'4,4':'4,3':1

    这将成为:'5,5':'5,4':1,'5,4':'4,4':1,'6,6':'6,63':1,'63,4':'4,4':1,'4,4':'4,3':2,'6,63':'63,4':1

    请注意此处的更改:

    目标='6,6':'6,63':1,'6,63':'63,4':1,'4,4':'4,3':1,'63,4':'4,4':1

    src='5,4':'4,4':1,'4,3':'3,4':1,'4,4':'4,9':1,'3,4':'4,4':1,'5,5':'5,4':1

    合并合并='5,4"'4,4,4":1;,'4,3"'3,4":1;,'6,63"'63,4":1;,'5,5":'5,4":1;,'6,6"'6,63":1;,'3,4":;'4,4":1;,'4,3,3,'6,6":;'6,6,63'6,63'6,63''6,63'6,63':1,6,63''6,63''6,63''6,63''6,63''1':1,'4,9':1

    别忘了还要添加导入以便复制:

    1
    import copy

    我能想到的最简单的方法是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #!/usr/bin/python

    from copy import deepcopy
    def dict_merge(a, b):
        if not isinstance(b, dict):
            return b
        result = deepcopy(a)
        for k, v in b.iteritems():
            if k in result and isinstance(result[k], dict):
                    result[k] = dict_merge(result[k], v)
            else:
                result[k] = deepcopy(v)
        return result

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

    print dict_merge(a,b)

    输出:

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