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

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

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


A B CD是树的叶子,就像{"info1":"value", "info2":"value2"}

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

在我的情况下,它表示目录/文件结构,其中节点为docs,而叶子为文件。

我想将它们合并以获得:

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


我不确定如何使用Python轻松做到这一点。

评论

对于任意深度的词典,您想要什么?您要y展平至c级还是什么?您的示例不完整。

在这里检查我的NestedDict类:stackoverflow.com/a/16296144/2334951它确实管理嵌套词典结构,例如合并及其他。

对寻求解决方案的所有人的警告:这个问题仅与嵌套字典有关。大多数答案不能正确处理结构中更复杂的命令列表的情况。如果您需要这样做,请尝试以下@Osiloke的答案:stackoverflow.com/a/25270947/1431660

另请参阅:python dpath merge

另请参阅:合并多个字典

#1 楼

这实际上是非常棘手的-尤其是当您需要在事物不一致时发出有用的错误消息,同时正确地接受重复但一致的条目时(这里没有其他答案了……)。

假设您不这样做如果有大量条目,则最简单的递归函数是:

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,可以像merge(dict(a), b)那样来命名。

agf指出(如下所示)您可能有两个以上的字典,在这种情况下,您可以使用:

reduce(merge, [dict1, dict2, dict3...])


所有内容都将添加到dict1。

[注意-我编辑了最初的答案以使第一个参数发生变化;使“ reduce”更易于解释]

python 3中的ps,您还需要from functools import reduce

评论


然后,您可以将其放入reduce或等效循环中,以处理任意数量的dict,而不是两个。但是,我不确定这是否满足他的要求(他不清楚),您最终得到了2:{'c':{'z':{'y':{'info1':'value' ,'info2':'value2'}}},'b':{'info1':'value','info2':'value2'}}对于他的第二个示例,我不确定他是否想要z和y是否展平?

–agf
2011年8月26日13:13

它们是目录结构,所以我不认为他/她想要展平什么?哦,对不起,错过了“多本字典”。是的,减少会很好。将添加。

– andrew cooke
2011年8月26日13:14



这正是我想要的!对不起,我还不够清楚...我以为我对Python没问题,似乎不:-//由于嵌套的字典,我需要一个递归函数,该函数可以工作,我可以理解:)我不知道似乎能够使它与减少工作...

– fdhex
2011年8月26日14:37



对于将列表作为dict下最终嵌套级别的任何人,您都可以执行此操作,而不用引发将两个列表连接在一起的错误:a [key] = a [key] + b [key]。感谢您的帮助。

–kevinmicke
17年5月7日在22:17

>如果要保留a,则可以像merge(dict(a),b)这样称呼它。请注意,嵌套的dict仍然会发生突变。为避免这种情况,请使用copy.deepcopy。

–rcorre
19年1月24日在16:27

#2 楼

这是使用生成器的一种简单方法:

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'}}


评论


如果要保留生成器主题,则可以链接(dict1.keys(),dict2.keys())

– andrew cooke
11年8月26日在14:05

那不会得到重复的钥匙吗?

– jterrace
2011年8月26日14:26

至少在我的一组数据上,这似乎可以完成工作,但是由于我对收率和生成器一无所知,我对为什么会迷失了很多,但我会更加努力,可能会有用!

– fdhex
2011年8月26日14:38

嗯,是的,它将得到重复的密钥。抱歉,您仍然需要将其包装在集合中。

– andrew cooke
2011年8月26日14:48



我发现这特别有帮助。但最好的办法是让函数将冲突作为参数来解决。

–mentatkgs
2012年5月24日18:26

#3 楼

这个问题的一个问题是,字典的值可以是任意复杂的数据。基于这些和其他答案,我想到了以下代码:


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\n" %(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文件,其中我只需要处理一个子集可能的数据类型。因此,我可以忽略元组和其他对象。对我来说,明智的合并逻辑意味着


替换标量
追加列表
通过添加缺少的键并更新现有键来合并字典

所有内容否则,意外情况将导致错误。

评论


太棒了同样适用于json转储。刚刚删除了错误处理。 (我敢肯定,可以为json做正确的事情)

– dgBP
16年2月24日,下午3:01

可以用isinstance(a,(str,unicode,int,long,float))替换“ isinstance”序列,不是吗?

–simahawk
17年3月22日在8:19

#4 楼


词典的字典合并

由于这是一个规范的问题(尽管有某些非一般性),所以我提供了规范的Python方法来解决此问题。
最简单的情况:“叶子是嵌套的字典,以空的字典结尾”:
d1 = {'a': {1: {'foo': {}}, 2: {}}}
d2 = {'a': {1: {}, 2: {'bar': {}}}}
d3 = {'b': {3: {'baz': {}}}}
d4 = {'a': {1: {'quux': {}}}}

这是最简单的递归情况,我建议两种朴素的方法:
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

我相信我宁愿选择第二个而不是第一个,但要记住,第一个的原始状态必须从其原始位置重建。用法:
>>> 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的值,即使第二个dict的值是None,第一个值是具有很多信息的字典。
d1 = {'a': {1: 'foo', 2: None}}
d2 = {'a': {1: None, 2: 'bar'}}
d3 = {'b': {3: 'baz'}}
d4 = {'a': {1: 'quux'}}

from collections.abc 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():
        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

现在
from functools import reduce
reduce(rec_merge, (d1, d2, d3, d4))

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

对原始应用问题:
我必须删除字母周围的花括号并将其放在单引号中,以使其成为合法的Python(否则它们将在Python 2.7+中设置为原义)并附加缺少的花括号:
dict1 = {1:{"a":'A'}, 2:{"b":'B'}}
dict2 = {2:{"c":'C'}, 3:{"d":'D'}}

rec_merge(dict1, dict2)现在返回:
{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}

匹配原始问题的期望结果(例如,在将{A}更改为'A'之后。)

#5 楼

您可以尝试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}}
 



有关选项的完整列表,请查看文档!


#6 楼

基于@andrew cooke。此版本处理字典的嵌套列表,还允许选择更新值
 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
 


评论


谢谢,这很有帮助。我一直在我的结构中有字典列表,其他解决方案无法正确合并。

–谢尔南德斯
2015年9月8日在8:39

#7 楼

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

#!/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"}, 2:{"b":"B"}}, {2:{"c":"C"}, 3:{"d":"D"}}))
print (merge_dicts({1:{"a":"A"}, 2:{"b":"B"}}, {1:{"a":"A"}, 2:{"b":"C"}}))


输出:

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


#8 楼

基于@andrew cooke的回答。
它可以更好地处理嵌套列表。

def deep_merge_lists(original, incoming):
    """
    Deep merge two lists. Modifies original.
    Recursively 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: Recursively call 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:
            original[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. Modifies original.
    For key conflicts if both values are:
     a. dict: Recursively call deep_merge_dicts on both values.
     b. list: Call 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处理清单:)

–vdwees
19年8月16日在16:36

#9 楼

如果您不知道字典的级别,那么我建议使用递归函数:

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


#10 楼

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


参数化的浅表合并函数merge(f)(a,b)使用
函数f合并两个字典ab


递归合并函数fmerge一起使用



实现
合并两个(非嵌套)字典的方式有很多。我个人喜欢
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的一种好方法是使用multidispatch,它可以定义函数,这些函数根据参数的类型沿不同的路径求值。 />
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)


示例
要合并两个嵌套的字典,只需使用merge(f)例如:
dict1 = {1:{"a":"A"},2:{"b":"B"}}
dict2 = {2:{"c":"C"},3:{"d":"D"}}
merge(f)(dict1, dict2)
#returns {1: {'a': 'A'}, 2: {'b': 'B', 'c': 'C'}, 3: {'d': 'D'}} 


注意:
这种方法的优点是:


该函数是由较小的函数构建的,每个较小的函数都执行同一操作
,这使代码更易于推理和测试


行为不是硬编码的,但是可以根据需要进行更改和扩展,从而提高了代码重用性(请参见下面的示例)。




定制
一些答案也被认为是包含列表的字典,例如其他(可能嵌套的)字典。在这种情况下,可能需要映射列表并根据位置合并它们。这可以通过向合并函数f添加另一个定义来完成:
import itertools
@dispatch(list, list)
def f(a,b):
    return [merge(f)(*arg) for arg in itertools.zip_longest(a, b)]


#11 楼

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

病毒学:简短,声明式且具有样式上的功能(递归,无突变)。

潜力缺点:这可能不是您要查找的合并。请查阅文档字符串以获取语义。

 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
        }
 


评论


非常有趣的答案,谢谢分享。您在return语句之后使用了什么语法?我不熟悉。

– dev_does_software
19年8月16日在11:08



#12 楼

安德鲁·库克斯答案有一个小问题:在某些情况下,当您修改返回的字典时,它会修改第二个参数b。特别是由于以下行:

if key in a:
    ...
else:
    a[key] = b[key]


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

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)


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

if isinstance(b[key], dict):
    a[key] = clone_dict(b[key])
else:
    a[key] = b[key]


其中clone_dict是:

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

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, {})


评论


为什么不使用Deepcopy而不是clone_dict?

– ArmandoPérezMarqués
2015年4月22日在17:27



因为python stdlib庞大而宏伟!我不知道这是存在的-加上编码是一件有趣的小事:-)

– andsens
15年4月22日在18:30

#13 楼

此版本的函数将占N个字典,仅占字典-无法传递不正确的参数,否则将引发TypeError。合并本身解决了关键冲突,而不是覆盖合并链下游的词典中的数据,它创建了一组值并将其追加到该值之后;不会丢失任何数据。

它可能不是页面上最有效的,但它是最彻底的,并且在合并2到N个字典时不会丢失任何信息。

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}

#14 楼

由于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,然后产生您想要的任何值。

#15 楼

简短的n-sweet:

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),因为它会原位更新字典dv中的键将覆盖d中的任何现有键(它不会尝试解释字典的内容)。

它也适用于其他(类似于“字典式”的)映射。

#16 楼

当然,代码将取决于您解决合并冲突的规则。这是一个可以接受任意数量的参数并将其递归合并到任意深度的版本,而无需使用任何对象突变。它使用以下规则解决合并冲突:


字典优先于非字典值({"foo": {...}}优先于{"foo": "bar"}
后来的参数优先于较早的参数(如果您按顺序合并{"a": 1}{"a", 2}{"a": 3},结果将是{"a": 3}

 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  
 


#17 楼

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

将嵌套字典视为树,我想要的是:


要更新a,如果在b的相应路径中找到叶子,则覆盖a的子树,以覆盖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中需要维护的路径为:


b
1 -> 'b' -> 'white'

2 -> 'd' -> 'black'

3 -> 'e'具有以下唯一且无冲突的路径:合并的地图。

#18 楼

看看toolz

import toolz
dict1={1:{"a":"A"},2:{"b":"B"}}
dict2={2:{"c":"C"},3:{"d":"D"}}
toolz.merge_with(toolz.merge,dict1,dict2)


给与

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


#19 楼

我有一个迭代的解决方案-与大型dict及其很多(例如jsons等)一起使用时效果要好得多:

import collections


def merge_dict_with_subdicts(dict1: dict, dict2: dict) -> dict:
    """
    similar behaviour to builtin dict.update - but knows how to handle nested dicts
    """
    q = collections.deque([(dict1, dict2)])
    while len(q) > 0:
        d1, d2 = q.pop()
        for k, v in d2.items():
            if k in d1 and isinstance(d1[k], dict) and isinstance(v, dict):
                q.append((d1[k], v))
            else:
                d1[k] = v

    return dict1


请注意,这将使用如果它们不是两个字典,则d2将覆盖d1。 (与python的dict.update()相同)

一些测试:

def test_deep_update():
    d = dict()
    merge_dict_with_subdicts(d, {"a": 4})
    assert d == {"a": 4}

    new_dict = {
        "b": {
            "c": {
                "d": 6
            }
        }
    }
    merge_dict_with_subdicts(d, new_dict)
    assert d == {
        "a": 4,
        "b": {
            "c": {
                "d": 6
            }
        }
    }

    new_dict = {
        "a": 3,
        "b": {
            "f": 7
        }
    }
    merge_dict_with_subdicts(d, new_dict)
    assert d == {
        "a": 3,
        "b": {
            "c": {
                "d": 6
            },
            "f": 7
        }
    }

    # test a case where one of the dicts has dict as value and the other has something else
    new_dict = {
        'a': {
            'b': 4
        }
    }
    merge_dict_with_subdicts(d, new_dict)
    assert d['a']['b'] == 4



我已经测试了约1200格-该方法花费了0.4秒,而递归解决方案则花费了约2.5秒的时间。

#20 楼

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

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给出的示例。要合并多个级别,应使用递归。

评论


他有任意深度的嵌套

–agf
11年8月26日在12:55

可以像dict2.iteritems()中的k,v那样简单地重写:dict1.setdefault(k,{})。update(v)。但是正如@agf指出的那样,这不会合并嵌套的字典。

–肖恩·钦(Shawn Chin)
11年8月26日在13:09

@agf:正确,因此OP似乎需要采用递归的解决方案。由于字典是可变的,这应该很容易做到。但是我认为这个问题还不够具体,无法说明当我们提出具有不同深度级别的地方(例如,尝试将{'a':'b'}与{'a':{'c' :'d'})。

–塔德克
11年8月26日在13:15

#21 楼

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

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"}, 2:{"b":"B"}}
dict2 = {2:{"c":"C"}, 3:{"d":"D"}}

#this helper function allows for recursion and the use of reduce
def f2(x, y):
    return dict(mergedicts(x, y, f2, lambda x: x))

print dict(mergedicts(dict1, dict2, f2, lambda x: x))
print dict(reduce(f2, [dict1, dict2]))


将函数作为参数传递是扩展jterrace解决方案以使其与其他所有行为一样的关键递归解决方案。

#22 楼

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

#!/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: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}


#23 楼

我在这里还有另一个略有不同的解决方案:

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的值,但是您可以通过一些麻烦轻松地覆盖它甚至可以抛出异常。 :)。

#24 楼

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()


#25 楼

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

#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)


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

target = {'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}}

也请注意此处的更改:

target = {'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}}

merge = {'5,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},'63,4':{'4,4':1},'4,4':{'4,3':1,'4,9':1}}

别忘了还要添加导入副本:

import copy


#26 楼

from collections import defaultdict
from itertools import chain

class DictHelper:

@staticmethod
def merge_dictionaries(*dictionaries, override=True):
    merged_dict = defaultdict(set)
    all_unique_keys = set(chain(*[list(dictionary.keys()) for dictionary in dictionaries]))  # Build a set using all dict keys
    for key in all_unique_keys:
        keys_value_type = list(set(filter(lambda obj_type: obj_type != type(None), [type(dictionary.get(key, None)) for dictionary in dictionaries])))
        # Establish the object type for each key, return None if key is not present in dict and remove None from final result
        if len(keys_value_type) != 1:
            raise Exception("Different objects type for same key: {keys_value_type}".format(keys_value_type=keys_value_type))

        if keys_value_type[0] == list:
            values = list(chain(*[dictionary.get(key, []) for dictionary in dictionaries]))  # Extract the value for each key
            merged_dict[key].update(values)

        elif keys_value_type[0] == dict:
            # Extract all dictionaries by key and enter in recursion
            dicts_to_merge = list(filter(lambda obj: obj != None, [dictionary.get(key, None) for dictionary in dictionaries]))
            merged_dict[key] = DictHelper.merge_dictionaries(*dicts_to_merge)

        else:
            # if override => get value from last dictionary else make a list of all values
            values = list(filter(lambda obj: obj != None, [dictionary.get(key, None) for dictionary in dictionaries]))
            merged_dict[key] = values[-1] if override else values

    return dict(merged_dict)



if __name__ == '__main__':
  d1 = {'aaaaaaaaa': ['to short', 'to long'], 'bbbbb': ['to short', 'to long'], "cccccc": ["the is a test"]}
  d2 = {'aaaaaaaaa': ['field is not a bool'], 'bbbbb': ['field is not a bool']}
  d3 = {'aaaaaaaaa': ['filed is not a string', "to short"], 'bbbbb': ['field is not an integer']}
  print(DictHelper.merge_dictionaries(d1, d2, d3))

  d4 = {"a": {"x": 1, "y": 2, "z": 3, "d": {"x1": 10}}}
  d5 = {"a": {"x": 10, "y": 20, "d": {"x2": 20}}}
  print(DictHelper.merge_dictionaries(d4, d5))


输出:

{'bbbbb': {'to long', 'field is not an integer', 'to short', 'field is not a bool'}, 
'aaaaaaaaa': {'to long', 'to short', 'filed is not a string', 'field is not a bool'}, 
'cccccc': {'the is a test'}}

{'a': {'y': 20, 'd': {'x1': 10, 'x2': 20}, 'z': 3, 'x': 10}}


评论


尽管此代码可以回答问题,但提供有关此代码为何和/或如何回答问题的其他上下文,可以提高其长期价值。

– xiawi
19年9月11日在8:57

我认为这是合并一个或多个嵌套字典的通用实现,同时考虑了将要标记的对象的类型

– Dorcioman
19年9月11日在9:02

#27 楼

以下函数将b合并为a。

def mergedicts(a, b):
    for key in b:
        if isinstance(a.get(key), dict) or isinstance(b.get(key), dict):
            mergedicts(a[key], b[key])
        else:
            a[key] = b[key]
    return a


#28 楼

还有一点细微的变化:

这里是一个基于python3集的纯深度更新功能。它一次循环遍历一个级别来更新嵌套字典,并调用自身来更新下一级别的字典值:

def deep_update(dict_original, dict_update):
    if isinstance(dict_original, dict) and isinstance(dict_update, dict):
        output=dict(dict_original)
        keys_original=set(dict_original.keys())
        keys_update=set(dict_update.keys())
        similar_keys=keys_original.intersection(keys_update)
        similar_dict={key:deep_update(dict_original[key], dict_update[key]) for key in similar_keys}
        new_keys=keys_update.difference(keys_original)
        new_dict={key:dict_update[key] for key in new_keys}
        output.update(similar_dict)
        output.update(new_dict)
        return output
    else:
        return dict_update


一个简单的示例:

x={'a':{'b':{'c':1, 'd':1}}}
y={'a':{'b':{'d':2, 'e':2}}, 'f':2}

print(deep_update(x, y))
>>> {'a': {'b': {'c': 1, 'd': 2, 'e': 2}}, 'f': 2}


#29 楼

另一个答案呢?!这也避免了突变/副作用:

 def merge(dict1, dict2):
    output = {}

    # adds keys from `dict1` if they do not exist in `dict2` and vice-versa
    intersection = {**dict2, **dict1}

    for k_intersect, v_intersect in intersection.items():
        if k_intersect not in dict1:
            v_dict2 = dict2[k_intersect]
            output[k_intersect] = v_dict2

        elif k_intersect not in dict2:
            output[k_intersect] = v_intersect

        elif isinstance(v_intersect, dict):
            v_dict2 = dict2[k_intersect]
            output[k_intersect] = merge(v_intersect, v_dict2)

        else:
            output[k_intersect] = v_intersect

    return output

 


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

assert dict3 == merge(dict1, dict2)