'Python: How to recursively merge 2 dictionaries? [duplicate]
Suppose we have 2 dictionaries:
a = {
"key1": "value1",
"key2": "value2",
"key3": {
"key3_1": "value3_1",
"key3_2": "value3_2"
}
}
b = {
"key1": "not_key1",
"key4": "something new",
"key3": {
"key3_1": "Definitely not value3_1",
"key": "new key without index?"
}
}
As a result of the merger, I need to get the following dictionary:
{
"key1": "not_key1",
"key2": "value2",
"key3": {
"key3_1": "Definitely not value3_1",
"key3_2": "value3_2",
"key": "new key without index?"
},
"key4": "something new"
}
I have this kind of code:
def merge_2_dicts(dict1, dict2):
for i in dict2:
if not type(dict2[i]) == dict:
dict1[i] = dict2[i]
else:
print(dict1[i], dict2[i], sep="\n")
dict1[i] = merge_2_dicts(dict1[i], dict2[i])
return dict1
It works and gives me the desired result, but I'm not sure if it can be done more simply. Is there an easier/shorter option?
Solution 1:[1]
I think your code is almost good. I see only issue what if key is missing in target dictionary?
def merge_dicts(tgt, enhancer):
for key, val in enhancer.items():
if key not in tgt:
tgt[key] = val
continue
if isinstance(val, dict):
merge_dicts(tgt[key], val)
else:
tgt[key] = val
return tgt
This code, do most of the same what you have written.
- check if key present in target dict, if not update regardless type.
- if val is dict, then we use recusion
- if val is not dict then update from enhancing dict
But I see still one issue what if in target dict value is string and in enhancer value is dict?
enhancer = {
"key3": {
"key3_1": "value3_1",
"key3_2": "value3_2"
}
}
tgt = {
"key3": "string_val"
}
Then it depends what do you prefer:
- Overwrite string with dict from enhancer:
def merge_dicts(tgt, enhancer):
for key, val in enhancer.items():
if key not in tgt:
tgt[key] = val
continue
if isinstance(val, dict):
if not isinstance(tgt[key], dict):
tgt[key] = dict()
merge_dicts(tgt[key], val)
else:
tgt[key] = val
return tgt
- Keep string value from target dict:
def merge_dicts(tgt, enhancer):
for key, val in enhancer.items():
if key not in tgt:
tgt[key] = val
continue
if isinstance(val, dict):
if not isinstance(tgt[key], dict):
continue
merge_dicts(tgt[key], val)
else:
tgt[key] = val
return tgt
Solution 2:[2]
Another solution:
from copy import deepcopy
from typing import Any
def is_all_dict(a1: Any, a2: Any) -> bool:
return isinstance(a1, dict) and isinstance(a2, dict)
def recursively_merge(d1: dict, d2: dict) -> dict:
d = deepcopy(d1)
for k, v2 in d2.items():
if (v := d.get(k)) and is_all_dict(v, v2):
sub_dicts = []
for sk, sv2 in v2.items():
if (sv := v.get(sk)) and is_all_dict(sv, sv2):
sub_dicts.append((sv, sv2))
else:
v[sk] = sv2
while sub_dicts:
sds = []
for v, v2 in sub_dicts:
for sk, sv2 in v2.items():
if (sv := v.get(sk)) and is_all_dict(sv, sv2):
sds.append((sv, sv2))
else:
v[sk] = sv2
sub_dicts = sds
else:
d[k] = v2
return d
Output:
In [26]: import pprint
In [27]: pprint.pprint(recursively_merge(a, b))
{'key1': 'not_key1',
'key2': 'value2',
'key3': {'key': 'new key without index?',
'key3_1': 'Definitely not value3_1',
'key3_2': 'value3_2'},
'key4': 'something new'}
Solution 3:[3]
If you want something really shorthand using dictionary comprehensions, you could use the below approach.
NB: By using .get(k)
in the if statement, we avoid having to check whether k
is in the dictionary
def merge_dicts(d1, d2):
check = lambda k, v: isinstance(d1.get(k), dict) and isinstance(v, dict)
return {**d1, **{k: merge_dicts(d1[k], d2[k]) if check(k, v) else v for k, v in d2.items()}}
Output:
>>> from pprint import pprint
>>> pprint(merge_dicts(a,b))
{'key1': 'not_key1',
'key2': 'value2',
'key3': {'key': 'new key without index?',
'key3_1': 'Definitely not value3_1',
'key3_2': 'value3_2'},
'key4': 'something new'}
Solution 4:[4]
This is a more simple solution to what you have. I believe you need at least 3.7+
c = {**a, **b}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | marc_s |
Solution 2 | Waket Zheng |
Solution 3 | |
Solution 4 | John Stud |