Comment puis-je fusionner deux dictionnaires Python en une seule expression?
Pour les dictionnaires x
et y
, z
devient un dictionnaire peu fusionné avec des valeurs de y
remplacement 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, y
viendra en second et ses valeurs remplaceront x
les 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 g
auront priorité sur les dits a
de 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_items
objets 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é. dict
rompu 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 copy
et update
ou 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
z = x | y