web-dev-qa-db-fra.com

Fusion de dictionnaires de dictionnaires

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 ABC 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.

86
fdhex

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

105
andrew cooke

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'}}
21
jterrace

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

  • remplacer les scalaires
  • ajouter des listes
  • fusionnez les dict en ajoutant les clés manquantes et en mettant à jour les clés existantes

Tout le reste et les imprévus entraînent une erreur.

18
Schlomo

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.

Cas le plus simple: "les feuilles sont des dessins imbriqués qui se terminent par des mots vides":

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': {}}}}

Cas complexe: "les feuilles sont d'un autre type:"

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'}}

Application à la question initiale:

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'.)

9
Aaron Hall

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 
7
Osiloke

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
6
Spencer Rathbun

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]
4
Vikas Kumar

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'}}
3
michael

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.

2
Guy Gangemi

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}

2
blakev

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]

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, {})
2
andsens

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é.

1
Tadeck

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:

  • Pour mettre à jour a afin que chaque chemin d'accès à chaque feuille de b soit représenté dans a
  • Pour écraser les sous-arbres de a si une feuille est trouvée dans le chemin correspondant dans b
    • Conservez l'invariant selon lequel tous les nœuds feuille 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.

1
mateor

Vue d'ensemble

L’approche suivante subdivise le problème de la fusion profonde de dict en: 

  1. 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

  2. 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={})]
1
Sascha
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()
0
wong steve

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").

0
Hans Bouwmeester

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. :).

0
Slava

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.

0
mentatkgs

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'}}
0
James Sapam

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
0
SlackSpace

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:

  • les dictionnaires ont priorité sur les valeurs non dictées ({"foo": {...}} a priorité sur {"foo": "bar"})
  • les arguments ultérieurs ont priorité sur les arguments précédents (si vous fusionnez {"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  
0
singingwolfboy

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
        }
0
David Schneider