Il y a un petit problème avec la réponse d'Andrew Cooke : Dans certains cas, elle modifie le deuxième argument b
lorsque vous modifiez le dict retourné. C'est en particulier à cause de cette ligne :
if key in a:
...
else:
a[key] = b[key]
If b[key]
est un dict
il sera simplement assigné à a
, c'est-à-dire toute modification ultérieure de cette dict
affectera à la fois a
et b
.
a={}
b={'1':{'2':'b'}}
c={'1':{'3':'c'}}
merge(merge(a,b), c) # {'1': {'3': 'c', '2': 'b'}}
a # {'1': {'3': 'c', '2': 'b'}} (as expected)
b # {'1': {'3': 'c', '2': 'b'}} <----
c # {'1': {'3': 'c'}} (unmodified)
Pour remédier à ce problème, il faudrait remplacer la ligne par celle-ci :
if isinstance(b[key], dict):
a[key] = clone_dict(b[key])
else:
a[key] = b[key]
Où clone_dict
est :
def clone_dict(obj):
clone = {}
for key, value in obj.iteritems():
if isinstance(value, dict):
clone[key] = clone_dict(value)
else:
clone[key] = value
return
Toujours. Cela ne tient évidemment pas compte list
, set
et d'autres choses, mais j'espère que cela illustre les pièges qui se présentent lorsque l'on essaie de fusionner des dicts
.
Et pour être complet, voici ma version, où vous pouvez le faire passer plusieurs fois. dicts
:
def merge_dicts(*args):
def clone_dict(obj):
clone = {}
for key, value in obj.iteritems():
if isinstance(value, dict):
clone[key] = clone_dict(value)
else:
clone[key] = value
return
def merge(a, b, path=[]):
for key in b:
if key in a:
if isinstance(a[key], dict) and isinstance(b[key], dict):
merge(a[key], b[key], path + [str(key)])
elif a[key] == b[key]:
pass
else:
raise Exception('Conflict at `{path}\''.format(path='.'.join(path + [str(key)])))
else:
if isinstance(b[key], dict):
a[key] = clone_dict(b[key])
else:
a[key] = b[key]
return a
return reduce(merge, args, {})