6278 votes

Comment fusionner deux dictionnaires en une seule expression (prendre l'union des dictionnaires) ?

Je veux fusionner deux dictionnaires en un nouveau dictionnaire.

x = {'a': 1, 'b': 2}
y = {'b': 10, 'c': 11}

z = merge(x, y)

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

Utilisation de x.update(y) modifie x en place au lieu de créer un nouveau dictionnaire. Je veux également que la gestion des conflits de la dernière chance de dict.update() également.

1 votes

Si vous voulez fusionner des espaces de noms (dicts en notation point), voyez ceci : stackoverflow.com/questions/56136549/

0 votes

La méthode d'implémentation choisie pour la méthode de mise à jour est déjà définie : la méthode change un attribut à l'intérieur de l'objet sans l'exposer à l'utilisateur, explicite (aka encapsulation). Ainsi vous pouvez implémenter une méthode pour retourner l'objet modifié directement à votre utilisation !

1751voto

Thomas Vander Stichele Points 16872

Dans votre cas, ce que vous pouvez faire est :

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

Cela permettra, comme vous le souhaitez, de mettre le dicton final en z et faire en sorte que la valeur de la clé b soit correctement remplacée par la seconde ( y ) la valeur du dict :

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

Si vous utilisez Python 2, vous pouvez même supprimer l'option list() appels. Pour créer des z :

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

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

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

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

10 votes

Ne l'utilisez pas car elle est très inefficace. (Voir les résultats de timeit ci-dessous.) Cela a pu être nécessaire à l'époque de Py2 si une fonction wrapper n'était pas une option, mais ces jours sont maintenant révolus.

719voto

Matthew Schinckel Points 15596

Une alternative :

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

100 votes

Pour clarifier pourquoi cela ne répond pas aux critères fournis par la question : ce n'est pas une expression unique et elle ne renvoie pas z.

18 votes

Disons-le ainsi : 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 tout à fait d'accord que Python n'est pas bon pour cela : il devrait y avoir un moyen beaucoup plus facile. Bien que cette réponse soit plus pythonique, est-elle vraiment si explicite ou claire ? Update ne fait pas partie des fonctions "essentielles" que les gens ont tendance à utiliser souvent.

8 votes

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

415voto

Carl Meyer Points 30736

Une autre option, plus concise :

z = dict(x, **y)

Note : cette réponse est devenue populaire, mais il est important de souligner que si y a des clés autres que des chaînes, le fait que cela fonctionne est un abus d'un détail d'implémentation de CPython, et cela ne fonctionne pas dans Python 3, ou dans PyPy, IronPython, ou Jython. Aussi, Guido n'est pas un fan . Je ne peux donc pas recommander cette technique pour du code portable compatible avec l'avenir ou avec des implémentations croisées, ce qui signifie qu'il faut l'éviter complètement.

3 votes

Fonctionne bien dans Python 3 et PyPy et PyPy 3 Je ne peux pas parler de Jython ou de Iron. Étant donné que ce modèle est explic explic explic explic explic (voir la troisième forme de constructeur dans cette documentation) Je dirais que ce n'est pas un "détail d'implémentation" mais une utilisation intentionnelle de la fonctionnalité.

15 votes

@amcgregor Vous avez manqué la phrase clé "si y a des clés autres que des chaînes de caractères". C'est ce qui ne fonctionne pas dans Python3 ; le fait que cela fonctionne dans CPython 2 est un détail d'implémentation sur lequel on ne peut pas compter. Si toutes vos clés sont garanties comme étant des chaînes de caractères, cette option est entièrement supportée.

251voto

Tony Meyer Points 4700

Ce ne sera probablement pas une réponse populaire, mais il est presque certain que vous ne voulez pas faire cela. Si vous voulez une copie qui est une fusion, alors utilisez copy (ou copie profonde en fonction de ce que vous voulez) et ensuite mettre à jour. Les deux lignes de code sont beaucoup plus lisibles - plus pythiques - que la création en une seule ligne avec .items() + .items(). L'explicite est mieux que l'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 une surcharge importante (deux grandes listes qui seront jetées dès que le dict fusionné sera créé). update() peut fonctionner plus efficacement, car il peut parcourir le second 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

Je pense que le léger ralentissement entre les deux premiers vaut la peine pour la lisibilité. De plus, les arguments de mots-clés pour la création de dictionnaires n'ont été ajoutés que dans Python 2.3, alors que copy() et update() fonctionneront dans les versions plus anciennes.

186voto

zaphod Points 1980

Dans une réponse complémentaire, vous avez demandé quelle était la performance relative de ces deux alternatives :

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

Sur ma machine, en tout cas (un x86_64 assez ordinaire exécutant Python 2.5.2), l'alternative z2 est non seulement plus court et plus simple, mais aussi nettement plus rapide. Vous pouvez le vérifier par vous-même en utilisant le timeit qui est fourni avec Python.

Exemple 1 : des dictionnaires identiques qui mettent en correspondance 20 entiers consécutifs avec 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

z2 gagne par un facteur de 3,5 environ. Les différents dictionnaires semblent donner des résultats très différents, mais z2 semble toujours s'en sortir. (Si vous obtenez des résultats incohérents pour le même essayez de passer dans -r avec un nombre plus grand que le 3 par défaut).

Exemple 2 : dictionnaires non chevauchants permettant de convertir 252 chaînes courtes en nombres 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 de 10 environ. C'est une grande victoire pour moi !

Après avoir comparé ces deux-là, je me suis demandé si z1 La mauvaise performance de l'entreprise pourrait être attribuée à la surcharge de la construction des deux listes d'items, ce qui m'a amené à me demander si cette variante 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

m'amènent à conclure que z3 est un peu plus rapide que z1 mais pas aussi rapide que z2 . Cela ne vaut vraiment pas la peine de faire tout ce travail d'écriture supplémentaire.

Il manque encore quelque chose d'important à cette discussion, à savoir une comparaison des performances de ces alternatives avec la méthode "évidente" de fusion de deux listes : l'utilisation de la fonction update mé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 le 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, z0 y z2 semblent avoir des performances essentiellement identiques. Pensez-vous que cela puisse être une coïncidence ? Je ne....

En fait, je dirais même qu'il est impossible pour un 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 utilise dict dans beaucoup d'endroits ; l'optimisation de ses opérations est une affaire importante.

Vous pourriez également écrire ceci comme

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

comme le fait Tony, mais (sans surprise) la différence de notation s'avère ne pas avoir d'effet mesurable sur les performances. Utilisez celle qui vous semble la plus appropriée. Bien sûr, il a tout à fait raison de souligner que la version à deux énoncés est beaucoup plus facile à comprendre.

8 votes

Cela ne fonctionne pas dans Python 3 ; items() n'est pas caténable, et iteritems n'existe pas.

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