在Python 2.7中合并多层嵌套字典的最佳方法

11
我有两个嵌套字典,我想将它们合并为一个(其中第二个字典覆盖第一个字典的值)。我看过很多关于合并“平面”(非嵌套)字典的优美解决方案,例如:
dict_result = dict1.copy()
dict_result.update(dict2)
或者
dict_result = dict(dict1.items() + dict2.items())

或者(我最喜欢的一个)

dict_result = dict(d1,**d2)

但是找不到合并多层嵌套字典的最有效的方法。

我试图避免递归。您有什么建议?


1
“我正在尝试避免递归。”祝你好运! - tobias_k
我希望Python能够提供一些内置方法来解决这个问题,但是我从未见过任何对我的目的有用的东西。 - mchfrnc
1
您可以通过使用具有显式堆栈的循环,将任何递归解决方案转换为迭代解决方案... - abarnert
1
顺便说一下,你最喜欢的那个实际上是三个中最糟糕的,因为如果d2的任何键不是字符串,它就会失败。虽然第二个也不太好,因为它构建了三个不必要的列表; dict(itertools.chain(dict1.iteritems(), dict2.iteritems()))可以避免这种情况。当然,在90%的情况下,您甚至不需要一个新字典,使用ChainMap会更简单和更有效...但无论如何,这都与您的问题无关。 - abarnert
2个回答

15

除非合并的字典深度严格限制,否则无法避免递归。1)此外,没有内置或库函数可以做到这一点(也就是说,我不知道任何一个),但实际上并不难。像这样的东西应该可以:

def merge(d1, d2):
    for k in d2:
        if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], dict):
            merge(d1[k], d2[k])
        else:
            d1[k] = d2[k]   

这段代码的作用是:遍历d2中的键,如果该键也可以在d1中找到并且两个键都是字典,则合并这些子字典,否则用d2中的值覆盖d1中的值。请注意,这将d1及其子字典原地修改,因此您可能需要在进行操作之前对其进行深度复制。

或者使用以下版本来创建合并后的副本:

def merge_copy(d1, d2):
    return {k: merge_copy(d1[k], d2[k]) if k in d1 and isinstance(d1[k], dict) and isinstance(d2[k], dict) else d2[k] for k in d2}

例子:

>>> d1 = {"foo": {"bar": 23, "blub": 42}, "flub": 17}
>>> d2 = {"foo": {"bar": 100}, "flub": {"flub2": 10}, "more": {"stuff": 111}}
>>> merge(d1, d2)
>>> print d1
{'foo': {'bar': 100, 'blub': 42}, 'flub': {'flub2': 10}, 'more': {'stuff': 111}}

1)你可以使用栈来实现迭代,但这只会使事情变得更加复杂,并且只应该用于避免递归深度过大的问题。


这是一种递归的原地更新,而不是像 OP 要求的递归复制合并...但是,即使您在解释中没有明确说明如何将一个转换为另一个,也应该非常明显。 :) - abarnert
谢谢!但是有一个小提醒(如果有人想要使用这段代码)- 在merge_copy()中参数应该被交换,正确的调用方式应该是:merge_copy(d2, d1) :) - mchfrnc
@mchfrnc 你确定吗?这个函数似乎工作正常。或者只是为了让 merge(d2, d1) 更像是“将d2合并到d1中”?我也考虑过这个问题,但由于你说“第二个”应该覆盖“第一个”,所以我把“覆盖”字典放在第二个位置。 - tobias_k

1

这是上述merge_copy函数的改进版本,适用于可以看作是父子合并的字典,您希望父级继承所有来自子级的值以创建一个新的字典。

def merge_copy(child, parent):
    '''returns parent updated with child values if exists'''
    d = {}
    for k in parent:
        if k in child and isinstance(child[k], dict) and isinstance(parent[k], dict):
            v = merge_copy(child[k], parent[k])
        elif k in child:
            v = child[k]
        else:
            v = parent[k]
        d[k] = v
    return d

此答案在迭代父字典的键集时,会删除任何子字典中独有的键。建议您迭代 set().union(parent, child) - Jim Browne

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接