递归合并dicts,以便将具有共享密钥的元素组合到一个列表中

Dep*_*epa 4 python union merge dictionary

我有两个想要合并的词组:

a = {"name": "john",
     "phone":"123123123",
     "owns": {"cars": "Car 1", "motorbikes": "Motorbike 1"}}

b = {"name": "john",
     "phone":"123",
     "owns": {"cars": "Car 2"}}
Run Code Online (Sandbox Code Playgroud)

如果a并且b在同一嵌套级别上具有公共密钥,则结果应该是一个列表,其中包含两个值,该值被指定为共享密钥的值.

结果应如下所示:

{"name": "john",
 "phone":["123123123","123"],
 "owns": {"cars": ["Car 1", "Car 2"], "motorbikes": "Motorbike 1"}}
Run Code Online (Sandbox Code Playgroud)

使用a.update(b)不起作用,因为它用共享值覆盖a共享值b,这样结果是这样的:

{'name': 'john', 'phone': '123', 'owns': {'cars': 'Car 2'}}
Run Code Online (Sandbox Code Playgroud)

目标是合并dicts而不覆盖,并保留与特定密钥相关的所有信息(在任何一个dicts中).

Oli*_*çon 5

使用递归,您可以构建一个字典理解来完成它.

此解决方案还考虑到您可能希望稍后合并两个以上的词典,在这种情况下展平值列表.

def update_merge(d1, d2):
    if isinstance(d1, dict) and isinstance(d2, dict):
        # Unwrap d1 and d2 in new dictionary to keep non-shared keys with **d1, **d2
        # Next unwrap a dict that treats shared keys
        # If two keys have an equal value, we take that value as new value
        # If the values are not equal, we recursively merge them
        return {
            **d1, **d2,
            **{k: d1[k] if d1[k] == d2[k] else update_merge(d1[k], d2[k])
            for k in {*d1} & {*d2}}
        }
    else:
        # This case happens when values are merged
        # It bundle values in a list, making sure
        # to flatten them if they are already lists
        return [
            *(d1 if isinstance(d1, list) else [d1]),
            *(d2 if isinstance(d2, list) else [d2])
        ]
Run Code Online (Sandbox Code Playgroud)

例:

a = {"name": "john", "phone":"123123123",
     "owns": {"cars": "Car 1", "motorbikes": "Motorbike 1"}}
b = {"name": "john", "phone":"123", "owns": {"cars": "Car 2"}}

update_merge(a, b)
# {'name': 'john',
#  'phone': ['123123123', '123'],
#  'owns': {'cars': ['Car 1', 'Car 2'], 'motorbikes': 'Motorbike 1'}}
Run Code Online (Sandbox Code Playgroud)

合并了两个以上对象的示例:

a = {"name": "john"}
b = {"name": "jack"}
c = {"name": "joe"}

d = update_merge(a, b)
d = update_merge(d, c)

d # {'name': ['john', 'jack', 'joe']}
Run Code Online (Sandbox Code Playgroud)