302 votes

Mise à jour de la valeur d'un dictionnaire imbriqué de profondeur variable

Je cherche un moyen de mettre à jour le dictionnaire 1 avec le contenu du dictionnaire 2 sans écraser le niveau A.

dictionary1={'level1':{'level2':{'levelA':0,'levelB':1}}}
update={'level1':{'level2':{'levelB':10}}}
dictionary1.update(update)
print dictionary1
{'level1': {'level2': {'levelB': 10}}}

Je sais que la mise à jour supprime les valeurs du niveau 2 car elle met à jour la clé la plus basse du niveau 1.

Comment puis-je résoudre ce problème, étant donné que le dictionnaire 1 et la mise à jour peuvent avoir n'importe quelle longueur ?

0 votes

L'imbrication est-elle toujours de trois niveaux de profondeur ou peut-on avoir une imbrication d'une profondeur arbitraire ?

1 votes

Il peut avoir n'importe quelle profondeur/longueur.

0 votes

Corrigez-moi si je me trompe, mais il semble que la solution idéale ici nécessite la mise en œuvre du modèle de conception composite.

10voto

djpinne Points 103

Cette question est ancienne, mais j'ai atterri ici en cherchant une solution de "fusion profonde". Les réponses ci-dessus ont inspiré ce qui suit. J'ai fini par écrire ma propre solution car il y avait des bogues dans toutes les versions que j'ai testées. Le point critique manqué était, à une certaine profondeur arbitraire des deux dicts d'entrée, pour une certaine clé, k, l'arbre de décision lorsque d[k] ou u[k] est pas une dictée était défectueuse.

De plus, cette solution ne nécessite pas de récursion, ce qui est plus symétrique avec la façon dont les dict.update() fonctionne, et renvoie None .

import collections
def deep_merge(d, u):
   """Do a deep merge of one dict into another.

   This will update d with values in u, but will not delete keys in d
   not found in u at some arbitrary depth of d. That is, u is deeply
   merged into d.

   Args -
     d, u: dicts

   Note: this is destructive to d, but not u.

   Returns: None
   """
   stack = [(d,u)]
   while stack:
      d,u = stack.pop(0)
      for k,v in u.items():
         if not isinstance(v, collections.Mapping):
            # u[k] is not a dict, nothing to merge, so just set it,
            # regardless if d[k] *was* a dict
            d[k] = v

        else:
            # note: u[k] is a dict
            if k not in d:
                # add new key into d
                d[k] = v
            elif not isinstance(d[k], collections.Mapping):
                # d[k] is not a dict, so just set it to u[k],
                # overriding whatever it was
                d[k] = v
            else:
                # both d[k] and u[k] are dicts, push them on the stack
                # to merge
                stack.append((d[k], v))

9voto

Fabio Caccamo Points 6

Il suffit d'utiliser python-benedict (Je l'ai fait) il a un merge (deepupdate) méthode utilitaire et bien d'autres. Il fonctionne avec python 2 / python 3 et il est bien testé.

from benedict import benedict

dictionary1=benedict({'level1':{'level2':{'levelA':0,'levelB':1}}})
update={'level1':{'level2':{'levelB':10}}}
dictionary1.merge(update)
print(dictionary1)
# >> {'level1':{'level2':{'levelA':0,'levelB':10}}}

Installation : pip install python-benedict

Documentation : https://github.com/fabiocaccamo/python-benedict

Note : Je suis l'auteur de ce projet

6voto

Jérôme Points 4207

Le code ci-dessous devrait résoudre le problème update({'k1': 1}, {'k1': {'k2': 2}}) question dans la réponse de @Alex Martelli de la bonne manière.

def deepupdate(original, update):
    """Recursively update a dict.

    Subdict's won't be overwritten but also updated.
    """
    if not isinstance(original, abc.Mapping):
        return update
    for key, value in update.items():
        if isinstance(value, abc.Mapping):
            original[key] = deepupdate(original.get(key, {}), value)
        else:
            original[key] = value
    return original

6voto

hobs Points 3020

Améliorations mineures apportées à Réponse d'@Alex qui permet de mettre à jour des dictionnaires de différentes profondeurs et de limiter la profondeur de la mise à jour dans le dictionnaire original imbriqué (mais la profondeur du dictionnaire de mise à jour n'est pas limitée). Seuls quelques cas ont été testés :

def update(d, u, depth=-1):
    """
    Recursively merge or update dict-like objects. 
    >>> update({'k1': {'k2': 2}}, {'k1': {'k2': {'k3': 3}}, 'k4': 4})
    {'k1': {'k2': {'k3': 3}}, 'k4': 4}
    """

    for k, v in u.iteritems():
        if isinstance(v, Mapping) and not depth == 0:
            r = update(d.get(k, {}), v, depth=max(depth - 1, -1))
            d[k] = r
        elif isinstance(d, Mapping):
            d[k] = u[k]
        else:
            d = {k: u[k]}
    return d

2 votes

Merci pour cela ! À quel cas d'utilisation le paramètre de profondeur pourrait-il s'appliquer ?

0 votes

@Matt lorsque vous avez des objets/dicts à une profondeur connue que vous ne voulez pas fusionner/mettre à jour, mais simplement écraser avec de nouveaux objets (comme remplacer un dict par une chaîne ou un flottant ou autre, au plus profond de votre dict).

2 votes

Cela ne fonctionne que si la mise à jour est au maximum d'un niveau plus profond que l'original. Par exemple, ceci échoue : update({'k1': 1}, {'k1': {'k2': {'k3': 3}}}) J'ai ajouté une réponse à cette question

4voto

helvete Points 553

J'ai utilisé la solution suggérée par @Alex Martelli, mais elle échoue.

TypeError 'bool' object does not support item assignment

lorsque les deux dictionnaires diffèrent dans le type de données à un certain niveau.

Dans le cas où au même niveau l'élément du dictionnaire d est juste un scalaire (c'est-à-dire Bool ) alors que l'élément du dictionnaire u est toujours un dictionnaire, la réaffectation échoue car il n'est pas possible d'assigner un dictionnaire à un scalaire (comme dans l'exemple suivant). True[k] ).

Une condition supplémentaire règle ce problème :

from collections import Mapping

def update_deep(d, u):
    for k, v in u.items():
        # this condition handles the problem
        if not isinstance(d, Mapping):
            d = u
        elif isinstance(v, Mapping):
            r = update_deep(d.get(k, {}), v)
            d[k] = r
        else:
            d[k] = u[k]

    return d

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