在Python中合并字典层级

2 投票
3 回答
1626 浏览
提问于 2025-04-18 00:10

我有两个字典,想做的事情有点特别。基本上,我想把它们合并。这听起来很简单。但是这两个字典都是层级结构的,我希望合并的时候,如果字典里的某个项本身也是一个字典,并且在两个字典中都存在,我想把这两个字典也合并。如果这个项不是字典,我希望第二个字典的值能覆盖第一个字典的值。大概是这样的:

a = {0: {0: "a"},
     1: [0, 1, 2]}

b = {0: {1: "b"},
     1: [3, 4, 5]}

Merge(a, b)

#output:
{0: {0: "a",
     1: "b"},
 1: [3, 4, 5]}

这样说清楚了吗?因为键“0”在字典a和b中都包含了一个字典,所以它们也合并了。但是在第二个键的情况下,它是一个列表,所以直接覆盖了。

所以我想我需要一个递归函数?但我不太确定该怎么开始。

谢谢!

编辑:我忘了提一个很重要的细节:

我需要一个在2.6.2和2.7.3版本都能用的函数。

3 个回答

0

我需要一个类似的东西,于是实现了一个更简单的递归解决方案。这个方案会直接在字典'd'中进行更新。

from collections.abc import MutableMapping

def merge(d, v):
    """
    Merge two dictionaries.
    
    Merge dict-like `v` into dict-like `d`. In case keys between them are the same, merge
    their sub-dictionaries where possible. Otherwise, values in `v` overwrite `d`.
    """
    for key in v:
        if key in d and isinstance(d[key], MutableMapping) and isinstance(v[key], MutableMapping):
            d[key] = merge(d[key], v[key])
        else:
            d[key] = v[key]
    return d

例子 1:

a = {0: {0: "a"},
     1: [0, 1, 2]}

b = {0: {1: "b"},
     1: [3, 4, 5]}
     
>>> merge(a, b)
{0: {0: 'a', 1: 'b'}, 1: [3, 4, 5]}

例子 2:

a = {0: {0: 'a'},
     1: [0, 1, 2],
     2: [9, 9],
     3: {'a': {1: 1, 2: 2}, 'b': [0, 1]}}

b = {0: {1: 'b'},
     1: [3, 4, 5],
     2: {22: 22, 33: 33},
     3: {'a': {2: 22, 3: 33}, 'b': [99, 88]}}
     
>>> merge(a, b) 
{0: {0: 'a', 1: 'b'},
 1: [3, 4, 5],
 2: {22: 22, 33: 33},
 3: {'a': {1: 1, 2: 22, 3: 33}, 'b': [99, 88]}}
0

嗯……只要你的嵌套结构不是随意的,就不需要用递归。

from itertools import chain

{k:(v if not isinstance(v,dict) else dict(chain(a[k].items(), v.items()))) for k,v in b.items()}
Out[10]: {0: {0: 'a', 1: 'b'}, 1: [3, 4, 5]}

(我这里用的是Python 3,如果你用的是Python 2,可以把.items换成.iteritems

因为这个方法有点啰嗦,所以总有一些巧妙的方式可以合并两个字典:

{k:(v if not isinstance(v,dict) else dict(a[k], **v)) for k,v in b.items()}
Out[11]: {0: {0: 'a', 1: 'b'}, 1: [3, 4, 5]}

你可以选择是否使用这种语法——虽然它很简洁,但有点利用了cPython的实现细节。

1

假设你可能有嵌套的字典(因为你在考虑递归),那么下面的代码应该可以工作,

from copy import deepcopy

def merge(a, b):
    if isinstance(b, dict) and isinstance(a, dict):
        a_and_b = a.viewkeys() & b.viewkeys()
        every_key = a.viewkeys() | b.viewkeys()
        return {k: merge(a[k], b[k]) if k in a_and_b else 
                   deepcopy(a[k] if k in a else b[k]) for k in every_key}
    return deepcopy(b)

merge(a, b) 的返回值可以理解为创建了一个 a 的(深层)副本,并且运行了一个递归版本的 a.update(b)


通过一些嵌套的例子,

a = {0: {0: 'a'},
     1: [0, 1, 2],
     2: [9, 9],
     3: {'a': {1: 1, 2: 2}, 'b': [0, 1]}}

b = {0: {1: 'b'},
     1: [3, 4, 5],
     2: {22: 22, 33: 33},
     3: {'a': {2: 22, 3: 33}, 'b': [99, 88]}}

merge(a, b) 产生的结果是,

{0: {0: 'a', 1: 'b'},
 1: [3, 4, 5],
 2: {22: 22, 33: 33},
 3: {'a': {1: 1, 2: 22, 3: 33}, 'b': [99, 88]}}

编辑:Python 2.6 版本

def merge(a, b):
    if isinstance(b, dict) and isinstance(a, dict):
        a_and_b = set(a).intersection(b)
        every_key = set(a).union(b)
        return dict((k, merge(a[k], b[k]) if k in a_and_b else
                     deepcopy(a[k] if k in a else b[k])) for k in every_key)
    return deepcopy(b)

撰写回答