Comment fusionner deux dictionnaires en une seule expression en Python?

4790

J'ai deux dictionnaires Python et je veux écrire une seule expression qui renvoie ces deux dictionnaires, fusionnés. La update()méthode serait ce dont j'ai besoin, si elle retournait son résultat au lieu de modifier un dictionnaire sur place.

>>> x = {'a': 1, 'b': 2}
>>> y = {'b': 10, 'c': 11}
>>> z = x.update(y)
>>> print(z)
None
>>> x
{'a': 1, 'b': 10, 'c': 11}

Comment puis-je obtenir ce dictionnaire fusionné final z, non x?

(Pour être plus clair, le dernier qui remporte la gestion des conflits dict.update()est ce que je recherche également.)

Carl Meyer
la source
1
Si vous utilisez l'alpha Python 3.9, utilisez simplementz = x | y
The Daleks

Réponses:

5709

Comment puis-je fusionner deux dictionnaires Python en une seule expression?

Pour les dictionnaires xet y, zdevient un dictionnaire peu fusionné avec des valeurs de yremplacement de celles de x.

  • En Python 3.5 ou supérieur:

    z = {**x, **y}
  • En Python 2, (ou 3.4 ou inférieur), écrivez une fonction:

    def merge_two_dicts(x, y):
        z = x.copy()   # start with x's keys and values
        z.update(y)    # modifies z with y's keys and values & returns None
        return z

    et maintenant:

    z = merge_two_dicts(x, y)
  • En Python 3.9.0a4 ou supérieur (date de sortie finale vers octobre 2020): PEP-584 , discuté ici , a été implémenté pour simplifier davantage ceci:

    z = x | y          # NOTE: 3.9+ ONLY

Explication

Supposons que vous ayez deux dicts et que vous souhaitiez les fusionner dans un nouveau dict sans modifier les dictons originaux:

x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}

Le résultat souhaité est d'obtenir un nouveau dictionnaire ( z) avec les valeurs fusionnées et les valeurs du second dict écrasant celles du premier.

>>> z
{'a': 1, 'b': 3, 'c': 4}

Une nouvelle syntaxe pour cela, proposée dans PEP 448 et disponible à partir de Python 3.5 , est

z = {**x, **y}

Et c'est en effet une seule expression.

Notez que nous pouvons également fusionner avec la notation littérale:

z = {**x, 'foo': 1, 'bar': 2, **y}

et maintenant:

>>> z
{'a': 1, 'b': 3, 'foo': 1, 'bar': 2, 'c': 4}

Il apparaît maintenant comme implémenté dans le calendrier de sortie de la version 3.5, PEP 478 , et il a maintenant fait son chemin dans Quoi de neuf dans Python 3.5 .

Cependant, étant donné que de nombreuses organisations sont toujours sur Python 2, vous souhaiterez peut-être le faire de manière rétrocompatible. La manière classique Pythonic, disponible dans Python 2 et Python 3.0-3.4, est de le faire en deux étapes:

z = x.copy()
z.update(y) # which returns None since it mutates z

Dans les deux approches, yviendra en second et ses valeurs remplaceront xles valeurs de, 'b'indiqueront donc3 dans notre résultat final.

Pas encore sur Python 3.5, mais vous voulez un seule expression

Si vous n'êtes pas encore sur Python 3.5, ou avez besoin d'écrire du code rétrocompatible, et que vous le souhaitez dans une seule expression , l'approche la plus performante et correcte est de le mettre dans une fonction:

def merge_two_dicts(x, y):
    """Given two dicts, merge them into a new dict as a shallow copy."""
    z = x.copy()
    z.update(y)
    return z

et puis vous avez une seule expression:

z = merge_two_dicts(x, y)

Vous pouvez également créer une fonction pour fusionner un nombre indéfini de dictés, de zéro à un très grand nombre:

def merge_dicts(*dict_args):
    """
    Given any number of dicts, shallow copy and merge into a new dict,
    precedence goes to key value pairs in latter dicts.
    """
    result = {}
    for dictionary in dict_args:
        result.update(dictionary)
    return result

Cette fonction fonctionnera en Python 2 et 3 pour tous les dict. par exemple donné des dits aà g:

z = merge_dicts(a, b, c, d, e, f, g) 

et les paires de valeurs clés dans gauront priorité sur les dits ade f, etc.

Critiques d'autres réponses

N'utilisez pas ce que vous voyez dans la réponse précédemment acceptée:

z = dict(x.items() + y.items())

Dans Python 2, vous créez deux listes en mémoire pour chaque dict, créez une troisième liste en mémoire avec une longueur égale à la longueur des deux premières réunies, puis supprimez les trois listes pour créer le dict. En Python 3, cela échouera car vous ajoutez deux dict_itemsobjets ensemble, pas deux listes -

>>> c = dict(a.items() + b.items())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'dict_items' and 'dict_items'

et vous devrez les créer explicitement sous forme de listes, par exemple z = dict(list(x.items()) + list(y.items())). C'est un gaspillage de ressources et de puissance de calcul.

De même, la prise de l'union de items()dans Python 3 ( viewitems()dans Python 2.7) échouera également lorsque les valeurs sont des objets non partageables (comme des listes, par exemple). Même si vos valeurs sont hachables, puisque les ensembles ne sont pas sémantiquement ordonnés, le comportement n'est pas défini en ce qui concerne la priorité. Alors ne fais pas ça:

>>> c = dict(a.items() | b.items())

Cet exemple montre ce qui se passe lorsque les valeurs ne sont pas partageables:

>>> x = {'a': []}
>>> y = {'b': []}
>>> dict(x.items() | y.items())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Voici un exemple où y devrait avoir la priorité, mais à la place la valeur de x est conservée en raison de l'ordre arbitraire des ensembles:

>>> x = {'a': 2}
>>> y = {'a': 1}
>>> dict(x.items() | y.items())
{'a': 2}

Un autre hack que vous ne devriez pas utiliser:

z = dict(x, **y)

Cela utilise le dict constructeur, et est très rapide et efficace en mémoire (même un peu plus que notre processus en deux étapes), mais à moins que vous ne sachiez précisément ce qui se passe ici (c'est-à-dire que le deuxième dict est transmis en tant qu'arguments de mot-clé au dict constructeur), il est difficile à lire, ce n'est pas l'usage prévu, et donc ce n'est pas Pythonic.

Voici un exemple d'utilisation corrigée dans django .

Les dictés sont destinés à prendre des clés lavables (par exemple des ensembles de frozensets ou des tuples), mais cette méthode échoue en Python 3 lorsque les clés ne sont pas des chaînes.

>>> c = dict(a, **b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings

De la liste de diffusion , Guido van Rossum, le créateur de la langue, a écrit:

Je suis d'accord pour déclarer illégal dict ({}, ** {1: 3}), car après tout c'est un abus du ** mécanisme.

et

Apparemment, dict (x, ** y) fait office de "hack cool" pour "appeler x.update (y) et renvoyer x". Personnellement, je le trouve plus méprisable que cool.

D'après ce que je comprends (ainsi que la compréhension du créateur de la langue ), l'utilisation prévue dict(**y)est de créer des dits à des fins de lisibilité, par exemple:

dict(a=1, b=10, c=11)

au lieu de

{'a': 1, 'b': 10, 'c': 11}

Réponse aux commentaires

Malgré ce que dit Guido, dict(x, **y)est conforme à la spécification dict, qui d'ailleurs. fonctionne à la fois pour Python 2 et 3. Le fait que cela ne fonctionne que pour les clés de chaîne est une conséquence directe du fonctionnement des paramètres de mot-clé et non une courte durée de dict. L'utilisation de l'opérateur ** à cet endroit n'est pas non plus un abus du mécanisme, en fait ** a été conçu précisément pour transmettre des dicts comme mots clés.

Encore une fois, cela ne fonctionne pas pour 3 lorsque les clés ne sont pas des chaînes. Le contrat d'appel implicite est que les espaces de noms prennent des dicts ordinaires, tandis que les utilisateurs doivent uniquement passer des arguments de mots clés qui sont des chaînes. Tous les autres callables l'ont appliqué. dictrompu cette cohérence dans Python 2:

>>> foo(**{('a', 'b'): None})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() keywords must be strings
>>> dict(**{('a', 'b'): None})
{('a', 'b'): None}

Cette incohérence était mauvaise compte tenu des autres implémentations de Python (Pypy, Jython, IronPython). Ainsi, il a été corrigé dans Python 3, car cette utilisation pourrait être un changement de rupture.

Je vous soumets qu'il est de l'incompétence malveillante d'écrire intentionnellement du code qui ne fonctionne que dans une seule version d'un langage ou qui ne fonctionne que compte tenu de certaines contraintes arbitraires.

Plus de commentaires:

dict(x.items() + y.items()) est toujours la solution la plus lisible pour Python 2. La lisibilité compte.

Ma réponse: en merge_two_dicts(x, y)fait, cela me semble beaucoup plus clair, si nous sommes réellement préoccupés par la lisibilité. Et il n'est pas compatible en aval, car Python 2 est de plus en plus obsolète.

{**x, **y}ne semble pas gérer les dictionnaires imbriqués. le contenu des clés imbriquées est simplement écrasé, non fusionné [...] J'ai fini par être brûlé par ces réponses qui ne fusionnent pas récursivement et j'ai été surpris que personne ne le mentionne. Dans mon interprétation du mot «fusion», ces réponses décrivent «la mise à jour d'un dict avec un autre», et non la fusion.

Oui. Je dois vous renvoyer à la question, qui demande une fusion superficielle de deux dictionnaires, les valeurs du premier étant écrasées par le second - en une seule expression.

En supposant deux dictionnaires de dictionnaires, l'un pourrait les fusionner récursivement en une seule fonction, mais vous devez faire attention à ne pas modifier les dict à partir de l'une ou l'autre source, et le moyen le plus sûr d'éviter cela est de faire une copie lors de l'attribution des valeurs. Les clés devant être lavables et donc généralement immuables, il est inutile de les copier:

from copy import deepcopy

def dict_of_dicts_merge(x, y):
    z = {}
    overlapping_keys = x.keys() & y.keys()
    for key in overlapping_keys:
        z[key] = dict_of_dicts_merge(x[key], y[key])
    for key in x.keys() - overlapping_keys:
        z[key] = deepcopy(x[key])
    for key in y.keys() - overlapping_keys:
        z[key] = deepcopy(y[key])
    return z

Usage:

>>> x = {'a':{1:{}}, 'b': {2:{}}}
>>> y = {'b':{10:{}}, 'c': {11:{}}}
>>> dict_of_dicts_merge(x, y)
{'b': {2: {}, 10: {}}, 'a': {1: {}}, 'c': {11: {}}}

Venir avec des contingences pour d'autres types de valeur est bien au-delà de la portée de cette question, donc je vais vous pointer vers ma réponse à la question canonique sur un "Fusion de dictionnaires de dictionnaires" .

Ad-hocs moins performants mais corrects

Ces approches sont moins performantes, mais elles fourniront un comportement correct. Ils seront beaucoup moins performants que copyet updateou le nouveau déballage car ils parcourent chaque paire clé-valeur à un niveau d'abstraction plus élevé, mais ils le font respectent l'ordre de préséance ( celui- ci dicts ont priorité)

Vous pouvez également chaîner les dict manuellement dans une compréhension de dict:

{k: v for d in dicts for k, v in d.items()} # iteritems in Python 2.7

ou en python 2.6 (et peut-être dès 2.4 lorsque les expressions de générateur ont été introduites):

dict((k, v) for d in dicts for k, v in d.items())

itertools.chain enchaînera les itérateurs sur les paires clé-valeur dans le bon ordre:

import itertools
z = dict(itertools.chain(x.iteritems(), y.iteritems()))

Analyse de performance

Je vais seulement faire l'analyse des performances des usages connus pour se comporter correctement.

import timeit

Ce qui suit se fait sur Ubuntu 14.04

En Python 2.7 (système Python):

>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.5726828575134277
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.163769006729126
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.iteritems(), y.iteritems()))))
1.1614501476287842
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
2.2345519065856934

En Python 3.5 (PPA des serpents morts):

>>> min(timeit.repeat(lambda: {**x, **y}))
0.4094954460160807
>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.7881555100320838
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.4525277839857154
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.items(), y.items()))))
2.3143140770262107
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
3.2069112799945287

Ressources sur les dictionnaires

Aaron Hall
la source
9
@MohammadAzim "chaînes uniquement" s'applique uniquement à l'expansion des arguments de mots clés dans les callables, et non à la syntaxe de décompression généralisée. Pour démontrer que cela fonctionne: {**{(0, 1):2}}->{(0, 1): 2}
Aaron Hall
37
des réponses courtes comme z = {**x, **y}vraiment me stimuler
pcko1
1
Cela peut être modifié lorsque PEP-0584 est accepté. Un nouvel opérateur d'union sera implémenté avec la syntaxe suivante:x | y
Callam Delaney
2
Lorsqu'une réponse nécessite un résumé en haut, elle est trop longue.
Gringo Suave
2
Salut, le haut est un résumé, oui. Dépend de vous. Le tout serait un excellent article de blog. Remarque Py 3.4 et ci-dessous sont EOL, 3.5 approchant EOL en 2020-09.
Gringo Suave
1618

Dans votre cas, vous pouvez:

z = dict(x.items() + y.items())

Cela, comme vous le souhaitez, mettra le dict final zet fera en sorte que la valeur de key bsoit correctement remplacée par la valeur du second ( y) dict:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = dict(x.items() + y.items())
>>> z
{'a': 1, 'c': 11, 'b': 10}

Si vous utilisez Python 3, c'est seulement un peu plus compliqué. Pour créer z:

>>> z = dict(list(x.items()) + list(y.items()))
>>> z
{'a': 1, 'c': 11, 'b': 10}

Si vous utilisez Python version 3.9.0a4 ou supérieure, vous pouvez directement utiliser:

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = x | y
print(z)

Output: {'a': 1, 'c': 11, 'b': 10}
Thomas Vander Stichele
la source
3
Ne l'utilisez pas car il est très inefficace. (Voir les résultats de timeit ci-dessous.) Il pouvait être nécessaire dans les jours Py2 si une fonction wrapper n'était pas une option, mais ces jours sont maintenant passés.
Gringo Suave
632

Une alternative:

z = x.copy()
z.update(y)
Matthew Schinckel
la source
83
Pour clarifier pourquoi cela ne répond pas aux critères fournis par la question: ce n'est pas une expression unique et cela ne renvoie pas z.
Alex
2
@neuronet chaque oneliner est généralement en train de déplacer du code qui doit arriver dans un composant différent et le résout là-bas. c'est certainement l'un des cas. mais d'autres langages ont des constructions plus agréables que python pour cela. et avoir une variante référentiellement transparente qui renvoie son élément est une bonne chose.
Alex
12
En d'autres termes, si vous devez mettre deux lignes de commentaires expliquant votre ligne de code aux personnes à qui vous remettez votre code ... l'avez-vous vraiment fait en une seule ligne? :) Je suis entièrement d'accord que Python n'est pas bon pour cela: il devrait y avoir un moyen beaucoup plus simple. Bien que cette réponse soit plus pythonique, est-ce vraiment explicite ou clair? Updaten'est pas l'une des fonctions «essentielles» que les gens ont tendance à utiliser beaucoup.
Eric
Eh bien, si les gens insistent pour en faire un oneliner, vous pouvez toujours faire (lambda z: z.update(y) or z)(x.copy()): P
TOWR
341

Une autre option, plus concise:

z = dict(x, **y)

Remarque : cela est devenu une réponse populaire, mais il est important de souligner que s'il y ya des clés non-chaîne, le fait que cela fonctionne du tout est un abus d'un détail d'implémentation CPython, et cela ne fonctionne pas dans Python 3, ou en PyPy, IronPython ou Jython. De plus, Guido n'est pas fan . Je ne peux donc pas recommander cette technique pour le code portable à compatibilité ascendante ou à implémentation croisée, ce qui signifie vraiment qu'elle devrait être évitée complètement.

Carl Meyer
la source
Fonctionne très bien dans Python 3 et PyPy et PyPy 3 , ne peut pas parler à Jython ou Iron. Étant donné que ce modèle est explicitement documenté (voir le troisième formulaire constructeur dans cette documentation), je dirais que ce n'est pas un "détail d'implémentation" mais une utilisation intentionnelle des fonctionnalités.
amcgregor
5
@amcgregor Vous avez manqué la phrase clé "si y a des clés autres que des chaînes". C'est ce qui ne fonctionne pas en Python3; le fait qu'il fonctionne dans CPython 2 est un détail d'implémentation sur lequel on ne peut pas se fier. Si toutes vos clés sont garanties sous forme de chaînes, c'est une option entièrement prise en charge.
Carl Meyer
214

Ce ne sera probablement pas une réponse populaire, mais vous ne voulez certainement pas faire cela. Si vous voulez une copie qui est une fusion, utilisez la copie (ou la copie profonde , selon ce que vous voulez), puis mettez à jour. Les deux lignes de code sont beaucoup plus lisibles - plus Pythonic - que la création d'une seule ligne avec .items () + .items (). Explicite vaut mieux qu'implicite.

De plus, lorsque vous utilisez .items () (avant Python 3.0), vous créez une nouvelle liste qui contient les éléments du dict. Si vos dictionnaires sont volumineux, cela représente beaucoup de frais généraux (deux grandes listes qui seront supprimées dès que le dict fusionné sera créé). update () peut fonctionner plus efficacement, car il peut parcourir le deuxième dict élément par élément.

En termes de temps :

>>> timeit.Timer("dict(x, **y)", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.52571702003479
>>> timeit.Timer("temp = x.copy()\ntemp.update(y)", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.694622993469238
>>> timeit.Timer("dict(x.items() + y.items())", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
41.484580039978027

IMO le minuscule ralentissement entre les deux premiers en vaut la peine pour la lisibilité. De plus, les arguments de mots clés pour la création de dictionnaire n'ont été ajoutés qu'en Python 2.3, tandis que copy () et update () fonctionneront dans les anciennes versions.

Tony Meyer
la source
150

Dans une réponse de suivi, vous avez posé des questions sur les performances relatives de ces deux alternatives:

z1 = dict(x.items() + y.items())
z2 = dict(x, **y)

Sur ma machine, au moins (un x86_64 assez ordinaire exécutant Python 2.5.2), l'alternative z2est non seulement plus courte et plus simple mais aussi beaucoup plus rapide. Vous pouvez le vérifier par vous-même en utilisant letimeit module fourni avec Python.

Exemple 1: dictionnaires identiques mappant 20 entiers consécutifs sur eux-mêmes:

% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z1=dict(x.items() + y.items())'
100000 loops, best of 3: 5.67 usec per loop
% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z2=dict(x, **y)' 
100000 loops, best of 3: 1.53 usec per loop

z2gagne par un facteur de 3,5 ou plus. Différents dictionnaires semblent donner des résultats assez différents, mais z2semblent toujours venir en tête. (Si vous obtenez des résultats incohérents pour le même test, essayez de passer -ravec un nombre supérieur au 3. par défaut.)

Exemple 2: dictionnaires sans chevauchement mappant 252 chaînes courtes en entiers et vice versa:

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z1=dict(x.items() + y.items())'
1000 loops, best of 3: 260 usec per loop
% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z2=dict(x, **y)'               
10000 loops, best of 3: 26.9 usec per loop

z2 gagne par un facteur d'environ 10. C'est une très grosse victoire dans mon livre!

Après avoir comparé ces deux, je me suis demandé si z1les mauvaises performances pouvaient être attribuées aux frais généraux de construction des deux listes d'articles, ce qui m'a amené à me demander si cette variation pourrait mieux fonctionner:

from itertools import chain
z3 = dict(chain(x.iteritems(), y.iteritems()))

Quelques tests rapides, par exemple

% python -m timeit -s 'from itertools import chain; from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z3=dict(chain(x.iteritems(), y.iteritems()))'
10000 loops, best of 3: 66 usec per loop

amène à conclure que z3c'est un peu plus rapide que z1, mais pas aussi vite quez2 . Certainement pas la peine de taper tous les caractères supplémentaires.

Il manque encore quelque chose d'important à cette discussion, qui est une comparaison des performances de ces alternatives avec la manière "évidente" de fusionner deux listes: en utilisant la updateméthode. Pour essayer de garder les choses sur un pied d'égalité avec les expressions, dont aucune ne modifie x ou y, je vais faire une copie de x au lieu de la modifier en place, comme suit:

z0 = dict(x)
z0.update(y)

Un résultat typique:

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z0=dict(x); z0.update(y)'
10000 loops, best of 3: 26.9 usec per loop

En d'autres termes, z0et z2semblent avoir des performances essentiellement identiques. Pensez-vous que cela pourrait être une coïncidence? Je ne....

En fait, j'irais jusqu'à prétendre qu'il est impossible pour le code Python pur de faire mieux que cela. Et si vous pouvez faire beaucoup mieux dans un module d'extension C, j'imagine que les gens de Python pourraient bien être intéressés à incorporer votre code (ou une variation de votre approche) dans le noyau Python. Python utilisedict dans de nombreux endroits; l'optimisation de ses opérations est un gros problème.

Vous pouvez également écrire ceci comme

z0 = x.copy()
z0.update(y)

comme Tony le fait, mais (sans surprise) la différence de notation se révèle n'avoir aucun effet mesurable sur les performances. Utilisez celui qui vous convient. Bien sûr, il a absolument raison de souligner que la version à deux déclarations est beaucoup plus facile à comprendre.

zaphod
la source
5
Cela ne fonctionne pas dans Python 3; items()n'est pas catenable et iteritemsn'existe pas.
Antti Haapala
127

Dans Python 3.0 etcollections.ChainMap versions ultérieures , vous pouvez utiliser les groupes qui regroupent plusieurs dictés ou autres mappages pour créer une seule vue pouvant être mise à jour:

>>> from collections import ChainMap
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = dict(ChainMap({}, y, x))
>>> for k, v in z.items():
        print(k, '-->', v)

a --> 1
b --> 10
c --> 11

Mise à jour pour Python 3.5 et versions ultérieures : vous pouvez utiliser la compression et la décompression du dictionnaire étendu PEP 448 . C'est simple et rapide:

>>> x = {'a':1, 'b': 2}
>>> y = y = {'b':10, 'c': 11}
>>> {**x, **y}
{'a': 1, 'b': 10, 'c': 11}
Raymond Hettinger
la source
3
Mais il faut être prudent lors de l'utilisation de ChainMap, il y a un inconvénient: si vous avez des clés en double, les valeurs du premier mappage sont utilisées et lorsque vous appelez a delon, un ChainMap c supprimera le premier mappage de cette clé.
Slayer
7
@Prerit Que pourriez-vous espérer d'autre? C'est le fonctionnement normal des espaces de noms chaînés. Considérez comment $ PATH fonctionne dans bash. La suppression d'un exécutable sur le chemin n'empêche pas un autre exécutable du même nom plus en amont.
Raymond Hettinger
2
@Raymond Hettinger Je suis d'accord, vient d'ajouter une mise en garde. La plupart des gens ne le savent peut-être pas. : D
Slayer
@Prerit Vous pourriez lancer pour dictéviter cela, c'est-à-dire:dict(ChainMap({}, y, x))
wjandrea
113

Je voulais quelque chose de similaire, mais avec la possibilité de spécifier comment les valeurs des clés en double ont été fusionnées, j'ai donc piraté cela (mais je ne l'ai pas testé de manière intensive). Évidemment, ce n'est pas une expression unique, mais c'est un appel de fonction unique.

def merge(d1, d2, merge_fn=lambda x,y:y):
    """
    Merges two dictionaries, non-destructively, combining 
    values on duplicate keys as defined by the optional merge
    function.  The default behavior replaces the values in d1
    with corresponding values in d2.  (There is no other generally
    applicable merge strategy, but often you'll have homogeneous 
    types in your dicts, so specifying a merge technique can be 
    valuable.)

    Examples:

    >>> d1
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1)
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1, lambda x,y: x+y)
    {'a': 2, 'c': 6, 'b': 4}

    """
    result = dict(d1)
    for k,v in d2.iteritems():
        if k in result:
            result[k] = merge_fn(result[k], v)
        else:
            result[k] = v
    return result
rcreswick
la source
88

Mettre à jour récursivement / en profondeur un dict

def deepupdate(original, update):
    """
    Recursively update a dict.
    Subdict's won't be overwritten but also updated.
    """
    for key, value in original.iteritems(): 
        if key not in update:
            update[key] = value
        elif isinstance(value, dict):
            deepupdate(value, update[key]) 
    return update

Manifestation:

pluto_original = {
    'name': 'Pluto',
    'details': {
        'tail': True,
        'color': 'orange'
    }
}

pluto_update = {
    'name': 'Pluutoo',
    'details': {
        'color': 'blue'
    }
}

print deepupdate(pluto_original, pluto_update)

Les sorties:

{
    'name': 'Pluutoo',
    'details': {
        'color': 'blue',
        'tail': True
    }
}

Merci rednaw pour les modifications.

Stan
la source
1
Cela ne répond pas à la question. La question demande clairement un nouveau dictionnaire, z, à partir des dictionnaires originaux, x et y, avec des valeurs de y remplaçant celles de x - pas un dictionnaire mis à jour. Cette réponse modifie y en place en ajoutant des valeurs de x. Pire encore, il ne copie pas ces valeurs, donc on pourrait encore modifier le dictionnaire modifié, y, et les modifications pourraient se refléter dans le dictionnaire x. @ Jérôme J'espère que ce code ne cause aucun bogue pour votre application - pensez au moins à utiliser deepcopy pour copier les valeurs.
Aaron Hall
1
@AaronHall a convenu que cela ne répond pas à la question. Mais cela répond à mon besoin. Je comprends ces limites, mais ce n'est pas un problème dans mon cas. À bien y penser, le nom est peut-être trompeur, car il pourrait évoquer une copie profonde, qu'il ne fournit pas. Mais il traite de l'imbrication profonde. Voici une autre implémentation du Martellibot: stackoverflow.com/questions/3232943/… .
Jérôme
72

La meilleure version que je pourrais penser sans utiliser la copie serait:

from itertools import chain
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
dict(chain(x.iteritems(), y.iteritems()))

C'est plus rapide dict(x.items() + y.items())mais pas aussi rapide que n = copy(a); n.update(b), du moins sur CPython. Cette version fonctionne également en Python 3 si vous passez iteritems()à items(), ce qui est automatiquement fait par l'outil 2to3.

Personnellement, je préfère cette version car elle décrit assez bien ce que je veux dans une seule syntaxe fonctionnelle. Le seul problème mineur est qu'il ne rend pas complètement évident que les valeurs de y ont priorité sur les valeurs de x, mais je ne pense pas qu'il soit difficile de comprendre cela.

driax
la source
71

Python 3.5 (PEP 448) permet une option de syntaxe plus agréable:

x = {'a': 1, 'b': 1}
y = {'a': 2, 'c': 2}
final = {**x, **y} 
final
# {'a': 2, 'b': 1, 'c': 2}

Ou même

final = {'a': 1, 'b': 1, **x, **y}

Dans Python 3.9, vous utilisez également | et | = avec l'exemple ci-dessous de PEP 584

d = {'spam': 1, 'eggs': 2, 'cheese': 3}
e = {'cheese': 'cheddar', 'aardvark': 'Ethel'}
d | e
# {'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}
Bilal Syed Hussain
la source
En quoi cette solution est-elle meilleure que la solution dict(x, **y)-s? Comme vous (@CarlMeyer) l'avez mentionné dans la note de votre propre réponse ( stackoverflow.com/a/39858/2798610 ), Guido considère cette solution comme illégale .
Blackeagle52
14
Guido n'aime pas dict(x, **y)pour la (très bonne) raison pour laquelle il s'appuie yuniquement sur des clés qui sont des noms d'argument de mot clé valides (sauf si vous utilisez CPython 2.7, où le constructeur dict triche). Cette objection / restriction ne s'applique pas au PEP 448, qui généralise la **syntaxe de décompression pour dicter des littéraux. Cette solution a donc la même concision que dict(x, **y), sans inconvénient.
Carl Meyer
62
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = dict(x.items() + y.items())
print z

Pour les éléments avec des clés dans les deux dictionnaires («b»), vous pouvez contrôler celui qui se retrouve dans la sortie en mettant celui-ci en dernier.

Greg Hewgill
la source
En python 3, vous obtiendrez TypeError: type (s) d'opérande non pris en charge pour +: 'dict_items' et 'dict_items' ... vous devez encapsuler chaque dict avec list () comme: dict (list (x.items ()) + list (y.items ()))
justSaid
49

Bien que la question ait déjà reçu plusieurs réponses, cette solution simple au problème n'a pas encore été répertoriée.

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z4 = {}
z4.update(x)
z4.update(y)

Il est aussi rapide que z0 et le mal z2 mentionné ci-dessus, mais facile à comprendre et à changer.

phobie
la source
3
mais ce sont trois déclarations plutôt qu'une expression
fortran
14
Oui! Les solutions à une expression mentionnées sont lentes ou mauvaises. Un bon code est lisible et maintenable. Le problème est donc la question et non la réponse. Nous devons demander la meilleure solution à un problème et non une solution sur une seule ligne.
phobie
7
Perdez le z4 = {}et changez la ligne suivante en z4 = x.copy()- mieux qu'un bon code ne fait pas de choses inutiles (ce qui le rend encore plus lisible et maintenable).
martineau
3
Votre suggestion changerait cela en réponse de Matthews. Bien que sa réponse soit bonne, je pense que la mienne est plus lisible et mieux maintenable. La ligne supplémentaire ne serait mauvaise que si elle coûtait du temps d'exécution.
phobie
47
def dict_merge(a, b):
  c = a.copy()
  c.update(b)
  return c

new = dict_merge(old, extras)

Parmi ces réponses louches et douteuses, cet exemple brillant est le seul et unique bon moyen de fusionner des textes en Python, approuvé par le dictateur à vie Guido van Rossum lui-même! Quelqu'un d'autre a suggéré la moitié de cela, mais ne l'a pas mis dans une fonction.

print dict_merge(
      {'color':'red', 'model':'Mini'},
      {'model':'Ferrari', 'owner':'Carl'})

donne:

{'color': 'red', 'owner': 'Carl', 'model': 'Ferrari'}
Sam Watkins
la source
39

Si vous pensez que les lambdas sont mauvais, ne lisez pas plus loin. Comme demandé, vous pouvez écrire la solution rapide et efficace en mémoire avec une seule expression:

x = {'a':1, 'b':2}
y = {'b':10, 'c':11}
z = (lambda a, b: (lambda a_copy: a_copy.update(b) or a_copy)(a.copy()))(x, y)
print z
{'a': 1, 'c': 11, 'b': 10}
print x
{'a': 1, 'b': 2}

Comme suggéré ci-dessus, utiliser deux lignes ou écrire une fonction est probablement une meilleure façon de procéder.

EMS
la source
33

Soyez pythonique. Utilisez une compréhension :

z={i:d[i] for d in [x,y] for i in d}

>>> print z
{'a': 1, 'c': 11, 'b': 10}
Robino
la source
1
En fonction:def dictmerge(*args): return {i:d[i] for d in args for i in d}
jessexknight
1
Enregistrez une recherche en itérant directement les paires clé / valeur:z={k: v for d in (x, y) for k, v in d.items()}
ShadowRanger
30

En python3, la itemsméthode ne renvoie plus une liste , mais plutôt une vue , qui agit comme un ensemble. Dans ce cas, vous devrez prendre l'union définie car la concaténation avec +ne fonctionnera pas:

dict(x.items() | y.items())

Pour un comportement semblable à python3 dans la version 2.7, la viewitemsméthode devrait fonctionner à la place de items:

dict(x.viewitems() | y.viewitems())

Je préfère de toute façon cette notation car il semble plus naturel de la considérer comme une opération d'union d'ensemble plutôt que comme une concaténation (comme le titre le montre).

Éditer:

Quelques points supplémentaires pour python 3. Tout d'abord, notez que l' dict(x, **y)astuce ne fonctionnera pas en python 3 à moins que les clés ne ysoient des chaînes.

De plus, la réponse Chainmap de Raymond Hettinger est assez élégante, car elle peut prendre un nombre arbitraire de dict comme arguments, mais à partir des documents, il semble qu'elle regarde séquentiellement une liste de tous les dicts pour chaque recherche:

Les recherches recherchent successivement les mappages sous-jacents jusqu'à ce qu'une clé soit trouvée.

Cela peut vous ralentir si vous avez beaucoup de recherches dans votre application:

In [1]: from collections import ChainMap
In [2]: from string import ascii_uppercase as up, ascii_lowercase as lo; x = dict(zip(lo, up)); y = dict(zip(up, lo))
In [3]: chainmap_dict = ChainMap(y, x)
In [4]: union_dict = dict(x.items() | y.items())
In [5]: timeit for k in union_dict: union_dict[k]
100000 loops, best of 3: 2.15 µs per loop
In [6]: timeit for k in chainmap_dict: chainmap_dict[k]
10000 loops, best of 3: 27.1 µs per loop

Donc, environ un ordre de grandeur plus lent pour les recherches. Je suis un fan de Chainmap, mais semble moins pratique où il peut y avoir de nombreuses recherches.

barbe
la source
22

Abus menant à une solution à une expression pour la réponse de Matthew :

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (lambda f=x.copy(): (f.update(y), f)[1])()
>>> z
{'a': 1, 'c': 11, 'b': 10}

Vous avez dit que vous vouliez une expression, j'ai donc abusé lambdade lier un nom et des tuples pour remplacer la limite d'une expression de lambda. N'hésitez pas à grincer des dents.

Vous pouvez également le faire bien sûr si vous ne vous souciez pas de le copier:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (x.update(y), x)[1]
>>> z
{'a': 1, 'b': 10, 'c': 11}
Claudiu
la source
22

Solution simple utilisant itertools qui préserve l'ordre (les derniers dits ont la priorité)

import itertools as it
merge = lambda *args: dict(it.chain.from_iterable(it.imap(dict.iteritems, args)))

Et c'est son utilisation:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> merge(x, y)
{'a': 1, 'b': 10, 'c': 11}

>>> z = {'c': 3, 'd': 4}
>>> merge(x, y, z)
{'a': 1, 'b': 10, 'c': 3, 'd': 4}
reubano
la source
16

Même si les réponses étaient bonnes pour ce superficiel dictionnaire , aucune des méthodes définies ici ne fait réellement une fusion de dictionnaire approfondie.

Voici des exemples:

a = { 'one': { 'depth_2': True }, 'two': True }
b = { 'one': { 'extra': False } }
print dict(a.items() + b.items())

On pourrait s'attendre à un résultat de quelque chose comme ceci:

{ 'one': { 'extra': False', 'depth_2': True }, 'two': True }

Au lieu de cela, nous obtenons ceci:

{'two': True, 'one': {'extra': False}}

L'entrée «one» aurait dû avoir «depth_2» et «extra» comme éléments dans son dictionnaire si c'était vraiment une fusion.

L'utilisation de la chaîne ne fonctionne pas non plus:

from itertools import chain
print dict(chain(a.iteritems(), b.iteritems()))

Résulte en:

{'two': True, 'one': {'extra': False}}

La fusion profonde que rcwesick a donnée crée également le même résultat.

Oui, cela fonctionnera pour fusionner les exemples de dictionnaires, mais aucun d'entre eux n'est un mécanisme générique pour fusionner. Je le mettrai à jour plus tard une fois que j'écrirai une méthode qui effectue une véritable fusion.

Thanh Lim
la source
11

(Pour Python2.7 * uniquement; il existe des solutions plus simples pour Python3 *.)

Si vous n'êtes pas opposé à l'importation d'un module de bibliothèque standard, vous pouvez le faire

from functools import reduce

def merge_dicts(*dicts):
    return reduce(lambda a, d: a.update(d) or a, dicts, {})

(Le or abit dans le lambdaest nécessaire car dict.updateil revient toujours Nonesur le succès.)

kjo
la source
11

Si cela ne vous dérange pas de muter x,

x.update(y) or x

Simple, lisible, performant. Vous savez update() toujours retourne None, ce qui est une fausse valeur. Ainsi, l'expression ci-dessus sera toujours évaluée xaprès sa mise à jour.

Les méthodes de mutation dans la bibliothèque standard (comme .update()) reviennent Nonepar convention, donc ce modèle fonctionnera aussi sur celles-ci. Si vous utilisez une méthode qui ne respecte pas cette convention, cela orpeut ne pas fonctionner. Mais, vous pouvez utiliser un affichage et un index de tuple pour en faire une seule expression à la place. Cela fonctionne quel que soit le premier élément évalué.

(x.update(y), x)[-1]

Si vous n'avez pas xencore de variable, vous pouvez utiliser lambdapour créer un local sans utiliser une instruction d'affectation. Cela revient à utiliser lambdacomme une expression let , qui est une technique courante dans les langages fonctionnels, mais peut-être unpythonic.

(lambda x: x.update(y) or x)({'a': 1, 'b': 2})

Bien que ce ne soit pas si différent de l'utilisation suivante du nouvel opérateur morse (Python 3.8+ uniquement):

(x := {'a': 1, 'b': 2}).update(y) or x

Si vous voulez une copie, le style PEP 448 est plus simple {**x, **y}. Mais si ce n'est pas disponible dans votre (ancienne) version Python, le motif let fonctionne ici aussi.

(lambda z: z.update(y) or z)(x.copy())

(C'est, bien sûr, équivalent à (z := x.copy()).update(y) or z, mais si votre version Python est suffisamment nouvelle pour cela, alors le style PEP 448 sera disponible.)

gilch
la source
10

En m'appuyant sur des idées ici et ailleurs, j'ai compris une fonction:

def merge(*dicts, **kv): 
      return { k:v for d in list(dicts) + [kv] for k,v in d.items() }

Utilisation (testé en python 3):

assert (merge({1:11,'a':'aaa'},{1:99, 'b':'bbb'},foo='bar')==\
    {1: 99, 'foo': 'bar', 'b': 'bbb', 'a': 'aaa'})

assert (merge(foo='bar')=={'foo': 'bar'})

assert (merge({1:11},{1:99},foo='bar',baz='quux')==\
    {1: 99, 'foo': 'bar', 'baz':'quux'})

assert (merge({1:11},{1:99})=={1: 99})

Vous pouvez utiliser un lambda à la place.

Bijou Trouvaille
la source
10

Le problème que j'ai avec les solutions répertoriées à ce jour est que, dans le dictionnaire fusionné, la valeur de la clé "b" est 10 mais, à mon avis, elle devrait être de 12. Dans cette optique, je présente ce qui suit:

import timeit

n=100000
su = """
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
"""

def timeMerge(f,su,niter):
    print "{:4f} sec for: {:30s}".format(timeit.Timer(f,setup=su).timeit(n),f)

timeMerge("dict(x, **y)",su,n)
timeMerge("x.update(y)",su,n)
timeMerge("dict(x.items() + y.items())",su,n)
timeMerge("for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k] ",su,n)

#confirm for loop adds b entries together
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]
print "confirm b elements are added:",x

Résultats:

0.049465 sec for: dict(x, **y)
0.033729 sec for: x.update(y)                   
0.150380 sec for: dict(x.items() + y.items())   
0.083120 sec for: for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]

confirm b elements are added: {'a': 1, 'c': 11, 'b': 12}
upandacross
la source
1
Vous pourriez être intéressé par cytoolz.merge_with( toolz.readthedocs.io/en/latest/… )
bli
10

C'est tellement idiot qui .updatene renvoie rien.
J'utilise simplement une fonction d'assistance simple pour résoudre le problème:

def merge(dict1,*dicts):
    for dict2 in dicts:
        dict1.update(dict2)
    return dict1

Exemples:

merge(dict1,dict2)
merge(dict1,dict2,dict3)
merge(dict1,dict2,dict3,dict4)
merge({},dict1,dict2)  # this one returns a new copy
GetFree
la source
10
from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))

Cela devrait résoudre votre problème.

reetesh11
la source
9

Cela peut être fait avec une seule compréhension de dict:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> { key: y[key] if key in y else x[key]
      for key in set(x) + set(y)
    }

À mon avis, la meilleure réponse pour la partie «expression unique» car aucune fonction supplémentaire n'est nécessaire, et elle est courte.

RemcoGerlich
la source
Je soupçonne cependant que les performances ne seront pas très bonnes; créer un ensemble de chaque dict puis itérer uniquement à travers les touches signifie une autre recherche de la valeur à chaque fois (bien que relativement rapide, augmente toujours l'ordre de la fonction pour la mise à l'échelle)
Breezer
2
tout dépend de la version du python que nous utilisons. En 3.5 et au-dessus {** x, ** y} donne le dictionnaire concaténé
Rashid Mv
9

Il y aura une nouvelle option lors de la sortie de Python 3.8 ( prévue pour le 20 octobre 2019 ), grâce à PEP 572: Expressions d'assignation . Le nouvel opérateur d'expression d'affectation :=vous permet d'affecter le résultat de la copyet de toujours l'utiliser pour appeler update, en laissant au code combiné une seule expression, plutôt que deux instructions, en changeant:

newdict = dict1.copy()
newdict.update(dict2)

à:

(newdict := dict1.copy()).update(dict2)

tout en se comportant de manière identique dans tous les sens. Si vous devez également renvoyer le résultat dict(vous avez demandé une expression renvoyant le dict; ce qui précède le crée et l'affecte newdict, mais ne le renvoie pas, vous ne pouvez donc pas l'utiliser pour passer un argument à une fonction telle quelle, à la la myfunc((newdict := dict1.copy()).update(dict2))) , puis ajoutez simplement or newdictà la fin (puisque updaterenvoie None, ce qui est faux, il évaluera et retournera ensuite newdictcomme résultat de l'expression):

(newdict := dict1.copy()).update(dict2) or newdict

Mise en garde importante: en général, je découragerais cette approche en faveur de:

newdict = {**dict1, **dict2}

L'approche de déballage est plus claire (pour quiconque connaît le déballage généralisé, ce que vous devriez faire ), ne nécessite aucun nom pour le résultat (il est donc beaucoup plus concis lors de la construction d'un temporaire qui est immédiatement transmis à un fonction ou inclus dans un list/ tuplelittéral ou similaire), et est presque certainement plus rapide également, étant (sur CPython) à peu près équivalent à:

newdict = {}
newdict.update(dict1)
newdict.update(dict2)

mais fait à la couche C, en utilisant l' dictAPI concrète , donc pas de recherche / liaison de méthode dynamique ou de surcharge de répartition d'appel de fonction (où(newdict := dict1.copy()).update(dict2) est inévitablement identique au comportement à deux lignes d'origine, effectuant le travail en étapes discrètes, avec une recherche dynamique / liaison / invocation de méthodes.

Il est également plus extensible, car la fusion de trois dicts est évidente:

 newdict = {**dict1, **dict2, **dict3}

où l'utilisation d'expressions d'affectation ne sera pas mise à l'échelle comme ça; le plus proche que vous pourriez obtenir serait:

 (newdict := dict1.copy()).update(dict2), newdict.update(dict3)

ou sans le tuple temporaire de Nones, mais avec des tests de véracité de chaque Nonerésultat:

 (newdict := dict1.copy()).update(dict2) or newdict.update(dict3)

soit de ce qui est évidemment beaucoup plus laid, et comprend en outre l' inefficacité (soit un gaspillage temporaire tuplede Nones pour la séparation de virgule, ou inutile test de truthiness de chacun updated » Noneéchange de orséparation).

Le seul véritable avantage de l'approche de l'expression d'affectation se produit si:

  1. Vous avez du code générique qui doit gérer à la fois sets et dicts (tous deux prennent en charge copyet update, donc le code fonctionne à peu près comme vous vous y attendez)
  2. Vous vous attendez à recevoir des objets de type dict arbitraires , pas seulement dictlui - même, et vous devez conserver le type et la sémantique du côté gauche (plutôt que de vous retrouver avec un simple dict). Bien que cela myspecialdict({**speciala, **specialb})puisse fonctionner, cela impliquerait un temporaire supplémentaire dict, et si myspecialdictdes fonctionnalités dictne peuvent pas être préservées (par exemple, les dicts réguliers préservent maintenant l'ordre en fonction de la première apparence d'une clé et la valeur en fonction de la dernière apparence d'une clé; celui qui préserve l'ordre basé sur le dernier apparence d'une clé, donc la mise à jour d'une valeur la déplace également à la fin), alors la sémantique serait erronée. Étant donné que la version de l'expression d'affectation utilise les méthodes nommées (qui sont probablement surchargées pour se comporter correctement), elle ne crée jamais undictpas du tout (sauf si dict1c'était déjà un dict), en préservant le type d'origine (et la sémantique du type d'origine), tout en évitant tout temporel.
ShadowRanger
la source
8
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> x, z = dict(x), x.update(y) or x
>>> x
{'a': 1, 'b': 2}
>>> y
{'c': 11, 'b': 10}
>>> z
{'a': 1, 'c': 11, 'b': 10}
John La Rooy
la source
Cette méthode écrase xavec sa copie. Si xest un argument de fonction, cela ne fonctionnera pas (voir l' exemple )
bartolo-otrit