J'ai besoin de fusionner plusieurs dictionnaires, voici ce que j'ai par exemple:
dict1 = {1:{"a":{A}}, 2:{"b":{B}}}
dict2 = {2:{"c":{C}}, 3:{"d":{D}}
Avec A
B
C
et D
étant les feuilles de l'arbre, comme {"info1":"value", "info2":"value2"}
Il y a un niveau inconnu (profondeur) de dictionnaires, il pourrait s'agir de {2:{"c":{"z":{"y":{C}}}}}
Dans mon cas, cela représente une structure de répertoires/fichiers avec des nœuds en tant que documents et des fichiers.
Je veux les fusionner pour obtenir:
dict3 = {1:{"a":{A}}, 2:{"b":{B},"c":{C}}, 3:{"d":{D}}}
Je ne sais pas comment je pourrais le faire facilement avec Python.
c'est en fait assez délicat - particulièrement si vous voulez un message d'erreur utile lorsque les choses sont incohérentes, tout en acceptant correctement les entrées dupliquées mais cohérentes (quelque chose qu'aucune autre réponse ne fait ici ....)
en supposant que vous n’ayez pas un grand nombre d’entrées, une fonction récursive est la plus simple:
def merge(a, b, path=None):
"merges b into a"
if path is None: 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 # same leaf value
else:
raise Exception('Conflict at %s' % '.'.join(path + [str(key)]))
else:
a[key] = b[key]
return a
# works
print(merge({1:{"a":"A"},2:{"b":"B"}}, {2:{"c":"C"},3:{"d":"D"}}))
# has conflict
merge({1:{"a":"A"},2:{"b":"B"}}, {1:{"a":"A"},2:{"b":"C"}})
notez que cela change a
- le contenu de b
est ajouté à a
(qui est également renvoyé). si vous voulez garder a
, vous pouvez l'appeler comme suit: merge(dict(a), b)
.
agf a souligné (ci-dessous) que vous pouvez avoir plus de deux dicts, auquel cas vous pouvez utiliser:
reduce(merge, [dict1, dict2, dict3...])
où tout sera ajouté à dict1.
[note - j'ai édité ma réponse initiale pour muter le premier argument; cela rend la "réduction" plus facile à expliquer]
ps en python 3, vous aurez également besoin de from functools import reduce
Voici un moyen facile de le faire en utilisant des générateurs:
def mergedicts(dict1, dict2):
for k in set(dict1.keys()).union(dict2.keys()):
if k in dict1 and k in dict2:
if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
yield (k, dict(mergedicts(dict1[k], dict2[k])))
else:
# If one of the values is not a dict, you can't continue merging it.
# Value from second dict overrides one in first and we move on.
yield (k, dict2[k])
# Alternatively, replace this with exception raiser to alert you of value conflicts
Elif k in dict1:
yield (k, dict1[k])
else:
yield (k, dict2[k])
dict1 = {1:{"a":"A"},2:{"b":"B"}}
dict2 = {2:{"c":"C"},3:{"d":"D"}}
print dict(mergedicts(dict1,dict2))
Cela imprime:
{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}
Un problème avec cette question est que les valeurs du dict peuvent être des données arbitrairement complexes. Sur la base de ces réponses et d’autres, j’ai trouvé ce code:
class YamlReaderError(Exception):
pass
def data_merge(a, b):
"""merges b into a and return merged result
NOTE: tuples and arbitrary objects are not handled as it is totally ambiguous what should happen"""
key = None
# ## debug output
# sys.stderr.write("DEBUG: %s to %s\n" %(b,a))
try:
if a is None or isinstance(a, str) or isinstance(a, unicode) or isinstance(a, int) or isinstance(a, long) or isinstance(a, float):
# border case for first run or if a is a primitive
a = b
Elif isinstance(a, list):
# lists can be only appended
if isinstance(b, list):
# merge lists
a.extend(b)
else:
# append to list
a.append(b)
Elif isinstance(a, dict):
# dicts must be merged
if isinstance(b, dict):
for key in b:
if key in a:
a[key] = data_merge(a[key], b[key])
else:
a[key] = b[key]
else:
raise YamlReaderError('Cannot merge non-dict "%s" into dict "%s"' % (b, a))
else:
raise YamlReaderError('NOT IMPLEMENTED "%s" into "%s"' % (b, a))
except TypeError, e:
raise YamlReaderError('TypeError "%s" in key "%s" when merging "%s" into "%s"' % (e, key, b, a))
return a
Mon cas d'utilisation est la fusion de fichiers YAML où je n'ai à traiter qu'avec un sous-ensemble de types de données possibles. Par conséquent, je peux ignorer des n-uplets et d'autres objets. Pour moi, une logique de fusion raisonnable signifie
Tout le reste et les imprévus entraînent une erreur.
Fusion des dictionnaires de dictionnaires
Comme il s'agit de la question canonique (malgré certaines non-généralités), je propose l'approche pythonique canonique pour résoudre ce problème.
d1 = {'a': {1: {'foo': {}}, 2: {}}}
d2 = {'a': {1: {}, 2: {'bar': {}}}}
d3 = {'b': {3: {'baz': {}}}}
d4 = {'a': {1: {'quux': {}}}}
C'est le cas le plus simple pour la récursivité, et je recommanderais deux approches naïves:
def rec_merge1(d1, d2):
'''return new merged dict of dicts'''
for k, v in d1.items(): # in Python 2, use .iteritems()!
if k in d2:
d2[k] = rec_merge1(v, d2[k])
d3 = d1.copy()
d3.update(d2)
return d3
def rec_merge2(d1, d2):
'''update first dict with second recursively'''
for k, v in d1.items(): # in Python 2, use .iteritems()!
if k in d2:
d2[k] = rec_merge2(v, d2[k])
d1.update(d2)
return d1
Je crois que je préférerais le second au premier, mais gardez à l'esprit que l'état d'origine du premier devrait être reconstruit à partir de son origine. Voici l'utilisation:
>>> from functools import reduce # only required for Python 3.
>>> reduce(rec_merge1, (d1, d2, d3, d4))
{'a': {1: {'quux': {}, 'foo': {}}, 2: {'bar': {}}}, 'b': {3: {'baz': {}}}}
>>> reduce(rec_merge2, (d1, d2, d3, d4))
{'a': {1: {'quux': {}, 'foo': {}}, 2: {'bar': {}}}, 'b': {3: {'baz': {}}}}
Ainsi, s’ils se terminent par des chiffres, c’est un simple cas de fusion des termes vides de fin. Sinon, ce n'est pas si trivial. Si les chaînes, comment les fusionnez-vous? Les ensembles peuvent être mis à jour de la même manière, nous pouvons donc appliquer ce traitement, mais nous perdons l'ordre dans lequel ils ont été fusionnés. Alors, l'ordre est-il important?
Ainsi, au lieu de plus d’informations, la méthode la plus simple consiste à leur donner le traitement de mise à jour standard si les deux valeurs ne sont pas dict: la valeur du second dict remplacera la première, même si la valeur de la seconde dict est None et la valeur de la première dict avec beaucoup d'informations.
d1 = {'a': {1: 'foo', 2: None}}
d2 = {'a': {1: None, 2: 'bar'}}
d3 = {'b': {3: 'baz'}}
d4 = {'a': {1: 'quux'}}
from collections import MutableMapping
def rec_merge(d1, d2):
'''
Update two dicts of dicts recursively,
if either mapping has leaves that are non-dicts,
the second's leaf overwrites the first's.
'''
for k, v in d1.items(): # in Python 2, use .iteritems()!
if k in d2:
# this next check is the only difference!
if all(isinstance(e, MutableMapping) for e in (v, d2[k])):
d2[k] = rec_merge(v, d2[k])
# we could further check types and merge as appropriate here.
d3 = d1.copy()
d3.update(d2)
return d3
Et maintenant
from functools import reduce
reduce(rec_merge, (d1, d2, d3, d4))
résultats
{'a': {1: 'quux', 2: 'bar'}, 'b': {3: 'baz'}}
J'ai dû supprimer les accolades entourant les lettres et les mettre entre guillemets pour que ce soit du Python légitime (sinon, ils seraient des littéraux définis dans Python 2.7+), ainsi que pour ajouter un accolade manquant:
dict1 = {1:{"a":'A'}, 2:{"b":'B'}}
dict2 = {2:{"c":'C'}, 3:{"d":'D'}}
et rec_merge(dict1, dict2)
renvoie maintenant:
{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}
Ce qui correspond au résultat souhaité de la question initiale (après avoir changé, par exemple le {A}
en 'A'
.)
Basé sur @andrew cooke. Cette version gère les listes imbriquées de dessins et permet également de mettre à jour les valeurs
def merge (a, b, chemin = Aucun, mise à jour = True): "http://stackoverflow.com/questions/7204805/python-dictionaries-of-dictionaries-merge" "fusionne b dans un" si path est None: path = [] pour la clé dans b: si vous entrez un: si isinstance (a [clé], dict) et isinstance (b [clé], dict): fusionner (a [clé], b [clé], chemin + [str (clé)]) Elif a [clé] == b [clé]: passe # même valeur de feuille Elif isinstance (a [clé], liste) et isinstance (b [clé], liste): pour idx, val en énumérer (b [clé]): a [clé] [idx] = fusion (a [clé] [idx], b [clé] [idx], chemin + [str (clé), str (idx)], update = update) Mise à jour Elif: a [clé] = b [clé] autre: raise Exception ('Conflit at% s'% '.'. join (chemin + [str (clé)])) autre: a [clé] = b [clé] retourne un
Si vous avez un niveau inconnu de dictionnaires, alors je suggérerais une fonction récursive:
def combineDicts(dictionary1, dictionary2):
output = {}
for item, value in dictionary1.iteritems():
if dictionary2.has_key(item):
if isinstance(dictionary2[item], dict):
output[item] = combineDicts(value, dictionary2.pop(item))
else:
output[item] = value
for item, value in dictionary2.iteritems():
output[item] = value
return output
Basé sur les réponses de @andrew cooke ..__, il gère mieux les listes imbriquées.
def deep_merge_lists(original, incoming):
"""
Deep merge two lists. Modifies original.
Reursively call deep merge on each correlated element of list.
If item type in both elements are
a. dict: call deep_merge_dicts on both values.
b. list: Calls deep_merge_lists on both values.
c. any other type: Value is overridden.
d. conflicting types: Value is overridden.
If length of incoming list is more that of original then extra values are appended.
"""
common_length = min(len(original), len(incoming))
for idx in range(common_length):
if isinstance(original[idx], dict) and isinstance(incoming[idx], dict):
deep_merge_dicts(original[idx], incoming[idx])
Elif isinstance(original[idx], list) and isinstance(incoming[idx], list):
deep_merge_lists(original[idx], incoming[idx])
else:
orginal[idx] = incoming[idx]
for idx in range(common_length, len(incoming)):
original.append(incoming[idx])
def deep_merge_dicts(original, incoming):
"""
Deep merge two dictionaries. Modfies original.
For key conflicts if both values are:
a. dict: Recursivley call deep_merge_dicts on both values.
b. list: Calls deep_merge_lists on both values.
c. any other type: Value is overridden.
d. conflicting types: Value is overridden.
"""
for key in incoming:
if key in original:
if isinstance(original[key], dict) and isinstance(incoming[key], dict):
deep_merge_dicts(original[key], incoming[key])
Elif isinstance(original[key], list) and isinstance(incoming[key], list):
deep_merge_lists(original[key], incoming[key])
else:
original[key] = incoming[key]
else:
original[key] = incoming[key]
Cette procédure récursive simple fusionnera un dictionnaire dans un autre tout en remplaçant les clés en conflit:
#!/usr/bin/env python2.7
def merge_dicts(dict1, dict2):
""" Recursively merges dict2 into dict1 """
if not isinstance(dict1, dict) or not isinstance(dict2, dict):
return dict2
for k in dict2:
if k in dict1:
dict1[k] = merge_dicts(dict1[k], dict2[k])
else:
dict1[k] = dict2[k]
return dict1
print (merge_dicts({1:{"a":"A"}, 2:{"b":"B"}}, {2:{"c":"C"}, 3:{"d":"D"}}))
print (merge_dicts({1:{"a":"A"}, 2:{"b":"B"}}, {1:{"a":"A"}, 2:{"b":"C"}}))
Sortie:
{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}
{1: {'a': 'A'}, 2: {'b': 'C'}}
Puisque dictviews prend en charge les opérations sur les ensembles, j’ai pu grandement simplifier la réponse de jterrace.
def merge(dict1, dict2):
for k in dict1.keys() - dict2.keys():
yield (k, dict1[k])
for k in dict2.keys() - dict1.keys():
yield (k, dict2[k])
for k in dict1.keys() & dict2.keys():
yield (k, dict(merge(dict1[k], dict2[k])))
Toute tentative de combinaison d'un dict avec un non dict (techniquement, un objet avec une méthode 'keys' et un objet sans une méthode 'keys') déclenche un AttributeError. Cela inclut à la fois l'appel initial à la fonction et les appels récursifs. C'est exactement ce que je voulais donc je l'ai laissé. Vous pouvez facilement attraper un AttributeErrors jeté par l'appel récursif et ensuite donner la valeur que vous souhaitez.
Cette version de la fonction prendra en compte un nombre N de dictionnaires, et uniquement des dictionnaires - aucun paramètre incorrect ne peut être transmis, sinon une erreur TypeError sera générée. La fusion elle-même comptabilise les conflits de clés et au lieu de remplacer les données d'un dictionnaire plus loin dans la chaîne de fusion, elle crée un ensemble de valeurs et y est ajoutée. aucune donnée n'est perdue.
Ce n'est peut-être pas le plus efficace sur la page, mais c'est le plus complet et vous ne perdrez aucune information lorsque vous fusionnez vos codes de 2 à N.
def merge_dicts(*dicts):
if not reduce(lambda x, y: isinstance(y, dict) and x, dicts, True):
raise TypeError, "Object in *dicts not of type dict"
if len(dicts) < 2:
raise ValueError, "Requires 2 or more dict objects"
def merge(a, b):
for d in set(a.keys()).union(b.keys()):
if d in a and d in b:
if type(a[d]) == type(b[d]):
if not isinstance(a[d], dict):
ret = list({a[d], b[d]})
if len(ret) == 1: ret = ret[0]
yield (d, sorted(ret))
else:
yield (d, dict(merge(a[d], b[d])))
else:
raise TypeError, "Conflicting key:value type assignment"
Elif d in a:
yield (d, a[d])
Elif d in b:
yield (d, b[d])
else:
raise KeyError
return reduce(lambda x, y: dict(merge(x, y)), dicts[1:], dicts[0])
print merge_dicts({1:1,2:{1:2}},{1:2,2:{3:1}},{4:4})
sortie: {1: [1, 2], 2: {1: 2, 3: 1}, 4: 4}
Andrew cookes répond qu'il y a un léger problème: dans certains cas, il modifie le deuxième argument b
lorsque vous modifiez le dict renvoyé. Plus précisément, c'est à cause de cette ligne:
if key in a:
...
else:
a[key] = b[key]
Si b[key]
est une dict
, il sera simplement attribué à a
, ce qui signifie que 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 résoudre ce problème, la ligne devrait être remplacée par ceci:
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
Encore. Cela ne représente évidemment pas list
, set
et d'autres choses, mais j'espère que cela illustre les pièges lors de la fusion dicts
.
Et par souci d’intégralité, voici ma version, où vous pouvez passer plusieurs 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, {})
Cela devrait aider à fusionner tous les éléments de dict2
dans dict1
:
for item in dict2:
if item in dict1:
for leaf in dict2[item]:
dict1[item][leaf] = dict2[item][leaf]
else:
dict1[item] = dict2[item]
S'il vous plaît testez-le et dites-nous si c'est ce que vous vouliez.
MODIFIER:
La solution mentionnée ci-dessus ne fusionne qu'un seul niveau, mais résout correctement l'exemple donné par OP. Pour fusionner plusieurs niveaux, vous devez utiliser la récursivité.
J'avais deux dictionnaires (a
et b
) pouvant chacun contenir un nombre quelconque de dictionnaires imbriqués. Je voulais les fusionner de manière récursive, b
prévalant sur a
.
Considérant les dictionnaires imbriqués comme des arbres, ce que je voulais, c'était:
a
afin que chaque chemin d'accès à chaque feuille de b
soit représenté dans a
a
si une feuille est trouvée dans le chemin correspondant dans b
b
restent des feuilles.Les réponses existantes étaient un peu compliquées à mon goût et ont laissé quelques détails sur l’étagère. J'ai piraté ensemble ce qui suit, qui passe des tests unitaires pour mon ensemble de données.
def merge_map(a, b):
if not isinstance(a, dict) or not isinstance(b, dict):
return b
for key in b.keys():
a[key] = merge_map(a[key], b[key]) if key in a else b[key]
return a
Exemple (formaté pour plus de clarté):
a = {
1 : {'a': 'red',
'b': {'blue': 'fish', 'yellow': 'bear' },
'c': { 'orange': 'dog'},
},
2 : {'d': 'green'},
3: 'e'
}
b = {
1 : {'b': 'white'},
2 : {'d': 'black'},
3: 'e'
}
>>> merge_map(a, b)
{1: {'a': 'red',
'b': 'white',
'c': {'orange': 'dog'},},
2: {'d': 'black'},
3: 'e'}
Les chemins dans b
qui devaient être maintenus étaient:
1 -> 'b' -> 'white'
2 -> 'd' -> 'black'
3 -> 'e'
.a
avait les chemins uniques et non conflictuels suivants:
1 -> 'a' -> 'red'
1 -> 'c' -> 'orange' -> 'dog'
ils sont donc toujours représentés dans la carte fusionnée.
Vue d'ensemble
L’approche suivante subdivise le problème de la fusion profonde de dict en:
Une fonction de fusion peu profonde paramétrée merge(f)(a,b)
qui utilise une fonction f
pour fusionner deux dessins a
et b
Une fonction de fusion récursive f
à utiliser avec merge
La mise en oeuvre
Une fonction permettant de fusionner deux dessins (non imbriqués) peut être écrite de nombreuses façons. Personnellement j'aime
def merge(f):
def merge(a,b):
keys = a.keys() | b.keys()
return {key:f(*[a.get(key), b.get(key)]) for key in keys}
return merge
Une bonne façon de définir une fonction de fusion récurrente appropriée f
consiste à utiliser multipledispatch qui permet de définir des fonctions qui s’évaluent selon différents chemins en fonction du type de leurs arguments.
from multipledispatch import dispatch
#for anything that is not a dict return
@dispatch(object, object)
def f(a, b):
return b if b is not None else a
#for dicts recurse
@dispatch(dict, dict)
def f(a,b):
return merge(f)(a,b)
Exemple
Pour fusionner deux plans imbriqués, utilisez simplement merge(f)
exemple:
dict1 = {1:{"a":"A"},2:{"b":"B"}}
dict2 = {2:{"c":"C"},3:{"d":"D"}}
merge(f)(dict1, dict2)
#returns {1: {'a': 'A'}, 2: {'b': 'B', 'c': 'C'}, 3: {'d': 'D'}}
Remarques:
Les avantages de cette approche sont les suivants:
La fonction est construite à partir de fonctions plus petites qui font chacune une seule chose Ce qui rend le code plus facile à raisonner et à tester.
Le comportement n'est pas codé en dur mais peut être modifié et étendu selon les besoins, ce qui améliore la réutilisation du code (voir exemple ci-dessous).
Personnalisation
Certaines réponses ont également considéré les dict contenant des listes, par exemple. d'autres dict (potentiellement imbriqués). Dans ce cas, on peut vouloir cartographier les listes et les fusionner en fonction de la position. Cela peut être fait en ajoutant une autre définition à la fonction de fusion f
:
import itertools
@dispatch(list, list)
def f(a,b):
return [merge(f)(*arg) for arg in itertools.Zip_longest(a,b,fillvalue={})]
class Utils(object):
"""
>>> a = { 'first' : { 'all_rows' : { 'pass' : 'dog', 'number' : '1' } } }
>>> b = { 'first' : { 'all_rows' : { 'fail' : 'cat', 'number' : '5' } } }
>>> Utils.merge_dict(b, a) == { 'first' : { 'all_rows' : { 'pass' : 'dog', 'fail' : 'cat', 'number' : '5' } } }
True
>>> main = {'a': {'b': {'test': 'bug'}, 'c': 'C'}}
>>> suply = {'a': {'b': 2, 'd': 'D', 'c': {'test': 'bug2'}}}
>>> Utils.merge_dict(main, suply) == {'a': {'b': {'test': 'bug'}, 'c': 'C', 'd': 'D'}}
True
"""
@staticmethod
def merge_dict(main, suply):
"""
获取融合的字典,以main为主,suply补充,冲突时以main为准
:return:
"""
for key, value in suply.items():
if key in main:
if isinstance(main[key], dict):
if isinstance(value, dict):
Utils.merge_dict(main[key], value)
else:
pass
else:
pass
else:
main[key] = value
return main
if __== '__main__':
import doctest
doctest.testmod()
Short-n-sweet:
from collections.abc import MutableMapping as Map
def nested_update(d, v):
"""
Nested update of dict-like 'd' with dict-like 'v'.
"""
for key in v:
if key in d and isinstance(d[key], Map) and isinstance(v[key], Map):
nested_update(d[key], v[key])
else:
d[key] = v[key]
Cela fonctionne comme (et est basé sur) la méthode dict.update
de Python. Il retourne None
(vous pouvez toujours ajouter return d
si vous préférez) à mesure qu'il met à jour dict d
sur place. Les clés dans v
écraseront toutes les clés existantes dans d
(le contenu de dict ne sera pas interprété).
Cela fonctionnera également pour d'autres mappages ("à la dictée").
J'ai une autre solution légèrement différente ici:
def deepMerge(d1, d2, inconflict = lambda v1,v2 : v2) :
''' merge d2 into d1. using inconflict function to resolve the leaf conflicts '''
for k in d2:
if k in d1 :
if isinstance(d1[k], dict) and isinstance(d2[k], dict) :
deepMerge(d1[k], d2[k], inconflict)
Elif d1[k] != d2[k] :
d1[k] = inconflict(d1[k], d2[k])
else :
d1[k] = d2[k]
return d1
Par défaut, il résout les conflits en faveur des valeurs du second dict, mais vous pouvez facilement le remplacer. Avec un peu de sorcellerie, vous pourrez même éliminer des exceptions. :).
J'ai testé vos solutions et décidé d'utiliser celle-ci dans mon projet:
def mergedicts(dict1, dict2, conflict, no_conflict):
for k in set(dict1.keys()).union(dict2.keys()):
if k in dict1 and k in dict2:
yield (k, conflict(dict1[k], dict2[k]))
Elif k in dict1:
yield (k, no_conflict(dict1[k]))
else:
yield (k, no_conflict(dict2[k]))
dict1 = {1:{"a":"A"}, 2:{"b":"B"}}
dict2 = {2:{"c":"C"}, 3:{"d":"D"}}
#this helper function allows for recursion and the use of reduce
def f2(x, y):
return dict(mergedicts(x, y, f2, lambda x: x))
print dict(mergedicts(dict1, dict2, f2, lambda x: x))
print dict(reduce(f2, [dict1, dict2]))
Passer des fonctions en tant que paramètres est essentiel pour que la solution jterrace se comporte comme toutes les autres solutions récursives.
Le moyen le plus simple auquel je puisse penser est:
#!/usr/bin/python
from copy import deepcopy
def dict_merge(a, b):
if not isinstance(b, dict):
return b
result = deepcopy(a)
for k, v in b.iteritems():
if k in result and isinstance(result[k], dict):
result[k] = dict_merge(result[k], v)
else:
result[k] = deepcopy(v)
return result
a = {1:{"a":'A'}, 2:{"b":'B'}}
b = {2:{"c":'C'}, 3:{"d":'D'}}
print dict_merge(a,b)
Sortie:
{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}
hé là, j’ai eu le même problème, mais j’ai pensé à une solution et je l’afficherai ici, au cas où il serait également utile pour d’autres, fusionnant essentiellement des dictionnaires imbriqués et ajoutant les valeurs, il me fallait calculer certaines probabilités on a très bien travaillé:
#used to copy a nested dict to a nested dict
def deepupdate(target, src):
for k, v in src.items():
if k in target:
for k2, v2 in src[k].items():
if k2 in target[k]:
target[k][k2]+=v2
else:
target[k][k2] = v2
else:
target[k] = copy.deepcopy(v)
en utilisant la méthode ci-dessus, nous pouvons fusionner:
target = {'6,6': {'6,63': 1}, '63, 4 ': {' 4,4 ': 1},' 4,4 ': {' 4,3 ': 1} , '6,63': {'63, 4 ': 1}}
src = {'5,4': {'4,4': 1}, '5,5': {'5,4': 1}, '4,4': {'4,3': 1} }
et cela deviendra: {'5,5': {'5,4': 1}, '5,4': {'4,4': 1}, '6,6': {'6 , 63 ': 1}, '63, 4': {'4,4': 1}, '4,4': {'4,3': 2}, '6,63': {'63, 4 ': 1}}
remarquez également les modifications ici:
target = {'6,6': {'6,63': 1}, '6,63': {'63, 4 ': 1},' 4,4 ': {' 4,3 ': 1} , '63, 4 ': {' 4,4 ': 1}}
src = {'5,4': {'4,4': 1}, '4,3': {'3,4': 1}, '4,4': {'4,9': 1} , '3,4': {'4,4': 1}, '5,5': {'5,4': 1}}
fusionner = {'5,4': {'4,4': 1}, '4,3': {'3,4': 1}, '6,63': {'63, 4 ': 1} , '5,5': {'5,4': 1}, '6,6': {'6,63': 1}, '3,4': {'4,4': 1}, ' 63,4 ': {' 4,4 ': 1},' 4,4 ': {' 4,3 ': 1,' 4,9 ': 1} }
n'oubliez pas d'ajouter également l'importation pour la copie:
import copy
Le code dépendra bien sûr de vos règles de résolution des conflits de fusion. Voici une version qui peut prendre un nombre arbitraire d'arguments et les fusionner de manière récursive à une profondeur arbitraire, sans utiliser aucune mutation d'objet. Il utilise les règles suivantes pour résoudre les conflits de fusion:
{"foo": {...}}
a priorité sur {"foo": "bar"}
){"a": 1}
, {"a", 2}
et {"a": 3}
dans l'ordre, le résultat sera {"a": 3}
)try:
from collections import Mapping
except ImportError:
Mapping = dict
def merge_dicts(*dicts):
"""
Return a new dictionary that is the result of merging the arguments together.
In case of conflicts, later arguments take precedence over earlier arguments.
"""
updated = {}
# grab all keys
keys = set()
for d in dicts:
keys = keys.union(set(d))
for key in keys:
values = [d[key] for d in dicts if key in d]
# which ones are mapping types? (aka dict)
maps = [value for value in values if isinstance(value, Mapping)]
if maps:
# if we have any mapping types, call recursively to merge them
updated[key] = merge_dicts(*maps)
else:
# otherwise, just grab the last value we have, since later arguments
# take precedence over earlier arguments
updated[key] = values[-1]
return updated
Au cas où quelqu'un voudrait encore une autre approche de ce problème, voici ma solution.
Vertus : style court, déclaratif et fonctionnel (récursif, ne subit aucune mutation).
Inconvénient potentiel : Ce n'est peut-être pas la fusion que vous recherchez. Consultez la docstring pour la sémantique.
def deep_merge(a, b):
"""
Merge two values, with `b` taking precedence over `a`.
Semantics:
- If either `a` or `b` is not a dictionary, `a` will be returned only if
`b` is `None`. Otherwise `b` will be returned.
- If both values are dictionaries, they are merged as follows:
* Each key that is found only in `a` or only in `b` will be included in
the output collection with its value intact.
* For any key in common between `a` and `b`, the corresponding values
will be merged with the same semantics.
"""
if not isinstance(a, dict) or not isinstance(b, dict):
return a if b is None else b
else:
# If we're here, both a and b must be dictionaries or subtypes thereof.
# Compute set of all keys in both dictionaries.
keys = set(a.keys()) | set(b.keys())
# Build output dictionary, merging recursively values with common keys,
# where `None` is used to mean the absence of a value.
return {
key: deep_merge(a.get(key), b.get(key))
for key in keys
}