209 votes

Comment fusionner des 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 A B C et D étant des feuilles de l'arbre, comme {"info1":"value", "info2":"value2"}

Il existe un niveau inconnu de dictionnaires, il pourrait s'agir de {2:{"c":{"z":{"y":{C}}}}}

Dans mon cas, il représente une structure de répertoires/fichiers dont les nœuds sont des documents et les feuilles des fichiers.

Je souhaite les fusionner pour obtenir :

 dict3 = {1:{"a":{A}}, 2:{"b":{B},"c":{C}}, 3:{"d":{D}}}

Je ne sais pas comment je pourrais faire cela facilement avec Python.

198voto

andrew cooke Points 20902

C'est en fait assez délicat - en particulier 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 (ce qu'aucune autre réponse ne fait ).

Si vous n'avez pas un grand nombre d'entrées, une fonction récursive est la plus simple :

from functools import reduce

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

noter qu'il s'agit d'une mutation a - le contenu de b sont ajoutés à a (qui est également renvoyée). Si vous souhaitez conserver a vous pourriez l'appeler ainsi merge(dict(a), b) .

agf a fait remarquer (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 modifié ma réponse initiale pour faire muter le premier argument ; cela rend la "réduction" plus facile à expliquer.

54voto

Travis Clarke Points 1715

Vous pouvez essayer mergedeep .


Installation

$ pip3 install mergedeep

Utilisation

from mergedeep import merge

a = {"keyA": 1}
b = {"keyB": {"sub1": 10}}
c = {"keyB": {"sub2": 20}}

merge(a, b, c) 

print(a)
# {"keyA": 1, "keyB": {"sub1": 10, "sub2": 20}}

Pour une liste complète d'options, consultez la rubrique documents !

47voto

jterrace Points 21939

Voici un moyen simple 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))

Cette empreinte :

{1: {'a': 'A'}, 2: {'c': 'C', 'b': 'B'}, 3: {'d': 'D'}}

29voto

Schlomo Points 51

L'un des problèmes posés par cette question est que les valeurs du dict peuvent être des éléments de 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 le suivant fusionner des fichiers YAML où je n'ai à traiter qu'un sous-ensemble de types de données possibles. Je peux donc ignorer les tuples et les o

  • r
  • a
  • m

E

16voto

Osiloke Points 11

Basé sur @andrew cooke. Cette version gère les listes imbriquées de dicts et permet également de mettre à jour les valeurs.

def merge(a, b, path=None, update=True):
    "http://stackoverflow.com/questions/7204805/python-dictionaries-of-dictionaries-merge"
    "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
            elif isinstance(a[key], list) and isinstance(b[key], list):
                for idx, val in enumerate(b[key]):
                    a[key][idx] = merge(a[key][idx], b[key][idx], path + [str(key), str(idx)], update=update)
            elif update:
                a[key] = b[key]
            else:
                raise Exception('Conflict at %s' % '.'.join(path + [str(key)]))
        else:
            a[key] = b[key]
    return a

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X