Skip to content

Instantly share code, notes, and snippets.

@angstwad
Last active March 1, 2024 23:53
Show Gist options
  • Save angstwad/bf22d1822c38a92ec0a9 to your computer and use it in GitHub Desktop.
Save angstwad/bf22d1822c38a92ec0a9 to your computer and use it in GitHub Desktop.
Recursive dictionary merge in Python
# Copyright 2016-2022 Paul Durivage
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import collections
def dict_merge(dct, merge_dct):
""" Recursive dict merge. Inspired by :meth:``dict.update()``, instead of
updating only top-level keys, dict_merge recurses down into dicts nested
to an arbitrary depth, updating keys. The ``merge_dct`` is merged into
``dct``.
:param dct: dict onto which the merge is executed
:param merge_dct: dct merged into dct
:return: None
"""
for k, v in merge_dct.iteritems():
if (k in dct and isinstance(dct[k], dict) and isinstance(merge_dct[k], dict)): #noqa
dict_merge(dct[k], merge_dct[k])
else:
dct[k] = merge_dct[k]
@Stargateur
Copy link

If you find this online and are looking to merge two dictionaries, please see the new dictionary merge features of Python 3.9 as discussed extensively in PEP 584.

I fail to see how this help

@tfeldmann
Copy link

tfeldmann commented Jan 23, 2022

In some of the solutions here the returned dict contains references to the input dicts. This could cause some serious bugs.

Testcase:

a = {}
b = {"a": {"b": 1, "c": 2}}
a = deep_merge(a, b)
assert a == b
b["a"]["b"] = 5
assert a != b

My version which passes this test (MIT license):

from copy import deepcopy

def deep_merge(a: dict, b: dict) -> dict:
    result = deepcopy(a)
    for bk, bv in b.items():
        av = result.get(bk)
        if isinstance(av, dict) and isinstance(bv, dict):
            result[bk] = deep_merge(av, bv)
        else:
            result[bk] = deepcopy(bv)
    return result

@ptrxyz
Copy link

ptrxyz commented Mar 18, 2022

@tfeldmann I am pretty sure that this:

        av = result.get("k")

should be this:

        av = result.get(bk)

@tfeldmann
Copy link

Yes of course. Corrected.

@tfeldmann
Copy link

Everything works fine if merge_dict is empty. Your version doesn't return a copy but the base dict itself, so that could lead to bugs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment