This function recursively walks the items and values of two dict like objects. At each level when a key exists in both, and each value is a dict, then the destination dict is updated from the source dict usiing the builtin dict.update method. After the operation all keys and values from the source, at any level, will be referenced in the destination.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | def recursiveupdate(dst, src):
"""Recursively update dst from src.
Recursion depth is bounded by the heap rather than the python
interpretor recursion limit.
>>> dst = dict(a=1,b=2,c=dict(ca=31, cc=33, cd=dict(cca=1)), d=4, f=6)
>>> src = dict(b='u2',c=dict(cb='u32', cd=dict(cda=dict(cdaa='u3411', cdab='u3412'))), e='u5')
>>> r = recursiveupdate(dst, src)
>>> assert r is dst
>>> assert r['a'] == 1 and r['d'] == 4 and r['f'] == 6
>>> assert r['b'] == 'u2' and r['e'] == 'u5'
>>> assert dst['c'] is r['c']
>>> assert dst['c']['cd'] is r['c']['cd']
>>> assert r['c']['cd']['cda']['cdaa'] == 'u3411'
>>> assert r['c']['cd']['cda']['cdab'] == 'u3412'
>>> from pprint import pprint; pprint(r)
{'a': 1,
'b': 'u2',
'c': {'ca': 31,
'cb': 'u32',
'cc': 33,
'cd': {'cca': 1, 'cda': {'cdab': 'u3412', 'cdaa': 'u3411'}}},
'd': 4,
'e': 'u5',
'f': 6}
"""
irecursiveupdate(dst, src.iteritems())
return dst
def irecursiveupdate(a, biter):
"""Recursively update dict `a` from `biter`
`biter` is assumed to be an iterable of the form::
[(k0, v0), (k1, v1), ..., (kN, vN)]
ie, the result of src.iteritems()
`a` is assumed to be a dict or dict like instance.
In the following `dst` is the intial value of `a` and `src` is
the initial value of `biter`.
For every key in src:
If that key is also in dst and
both dst[k] and src[k] are dicts then:
recursiveupdate(dst[k], (src[k]))
otherwise:
dst[k] = src[k]
"""
try:
stack = []
while biter:
for (bk,bv) in biter:
if (bk in a
and isinstance(bv, dict)
and isinstance(a[bk], dict)):
stack.append((biter, a)) # current -> parent
break
else:
a[bk] = bv
else:
while not biter:
biter, a = stack.pop() # current <- parent
continue
biter, a = bv.iteritems(), a[bk]
except IndexError:
pass
if __name__ == '__main__':
import doctest
doctest.testmod()
|
A typical use of this recipe is for merging configuration, option and default value trees. Libraries like ConfgiObj support this sort of thing and a lot more besides. However, if all you want is a merge based on pythons dict.update semantics then this recipe may be all you need.
My version: