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.

4voto

Sultan Alotaibi Points 44

Merci à plaques de cuisson pour son commentaire sur Alex's réponse . En effet, update({'k1': 1}, {'k1': {'k2': 2}}) causera TypeError: 'int' object does not support item assignment.

Nous devons vérifier les types des valeurs d'entrée au début de la fonction. Je propose donc la fonction suivante, qui devrait résoudre ce problème (et d'autres).

Python 3 :

from collections.abc import Mapping

def deep_update(d1, d2):
    if all((isinstance(d, Mapping) for d in (d1, d2))):
        for k, v in d2.items():
            d1[k] = deep_update(d1.get(k), v)
        return d1
    return d2

3voto

noragen Points 35

Il se peut que vous tombiez sur un dictionnaire non standard, comme moi aujourd'hui, qui ne possède pas l'attribut iteritems-Attribute. Dans ce cas, il est facile d'interpréter ce type de dictionnaire comme un dictionnaire standard. Par exemple : Python 2.7 :

    import collections
    def update(orig_dict, new_dict):
        for key, val in dict(new_dict).iteritems():
            if isinstance(val, collections.Mapping):
                tmp = update(orig_dict.get(key, { }), val)
                orig_dict[key] = tmp
            elif isinstance(val, list):
                orig_dict[key] = (orig_dict[key] + val)
            else:
                orig_dict[key] = new_dict[key]
        return orig_dict

    import multiprocessing
    d=multiprocessing.Manager().dict({'sample':'data'})
    u={'other': 1234}

    x=update(d, u)
    x.items()

Python 3.8 :

    def update(orig_dict, new_dict):
        orig_dict=dict(orig_dict)
        for key, val in dict(new_dict).items():
            if isinstance(val, collections.abc.Mapping):
                tmp = update(orig_dict.get(key, { }), val)
                orig_dict[key] = tmp
            elif isinstance(val, list):
                orig_dict[key] = (orig_dict[key] + val)
            else:
                orig_dict[key] = new_dict[key]
        return orig_dict

    import collections
    import multiprocessing
    d=multiprocessing.Manager().dict({'sample':'data'})
    u={'other': 1234, "deeper": {'very': 'deep'}}

    x=update(d, u)
    x.items()

3voto

panda-34 Points 2094

Dans aucune de ces réponses, les auteurs ne semblent comprendre le concept de mise à jour d'un objet stocké dans un dictionnaire, ni même celui d'itération sur les éléments du dictionnaire (par opposition aux clés). J'ai donc dû en écrire une qui ne fasse pas de stockages et de récupérations tautologiques inutiles de dictionnaires. Les dicts sont supposés stocker d'autres dicts ou des types simples.

def update_nested_dict(d, other):
    for k, v in other.items():
        if isinstance(v, collections.Mapping):
            d_v = d.get(k)
            if isinstance(d_v, collections.Mapping):
                update_nested_dict(d_v, v)
            else:
                d[k] = v.copy()
        else:
            d[k] = v

Ou même un plus simple fonctionnant avec n'importe quel type :

def update_nested_dict(d, other):
    for k, v in other.items():
        d_v = d.get(k)
        if isinstance(v, collections.Mapping) and isinstance(d_v, collections.Mapping):
            update_nested_dict(d_v, v)
        else:
            d[k] = deepcopy(v) # or d[k] = v if you know what you're doing

3voto

thuzhf Points 503

Mise à jour de la réponse de @Alex Martelli pour corriger un bug dans son code afin de rendre la solution plus robuste :

def update_dict(d, u):
    for k, v in u.items():
        if isinstance(v, collections.Mapping):
            default = v.copy()
            default.clear()
            r = update_dict(d.get(k, default), v)
            d[k] = r
        else:
            d[k] = v
    return d

La clé est que nous voulons souvent créer la même type à la récursion, donc ici nous utilisons v.copy().clear() mais pas {} . Et ceci est particulièrement utile si le dict ici est de type collections.defaultdict qui peuvent avoir différents types de default_factory s.

Notez également que le u.iteritems() a été modifié en u.items() sur Python3 .

3voto

honmaple Points 659
def update(value, nvalue):
    if not isinstance(value, dict) or not isinstance(nvalue, dict):
        return nvalue
    for k, v in nvalue.items():
        value.setdefault(k, dict())
        if isinstance(v, dict):
            v = update(value[k], v)
        value[k] = v
    return value

utiliser dict ou collections.Mapping

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