Comment fusionner des dictionnaires en Python?

90
d3 = dict(d1, **d2)

Je comprends que cela fusionne le dictionnaire. Mais est-ce unique? Et si d1 a la même clé que d2 mais une valeur différente? Je voudrais que d1 et d2 soient fusionnés, mais d1 a la priorité s'il y a une clé en double.

TIMEX
la source
9
Veuillez noter que cette astuce est considérée comme un abus de **passage d'arguments de mot - clé à moins que toutes les clés de d2soient des chaînes. Si toutes les clés de ne d2sont pas des chaînes, cela échoue dans Python 3.2 et dans des implémentations alternatives de Python telles que Jython, IronPython et PyPy. Voir, par exemple, mail.python.org/pipermail/python-dev/2010-April/099459.html .
Mark Dickinson

Réponses:

152

Vous pouvez utiliser la .update()méthode si vous n'avez plus besoin de l'original d2:

Mettez à jour le dictionnaire avec les paires clé / valeur d'autres clés , en écrasant les clés existantes . Retour None.

Par exemple:

>>> d1 = {'a': 1, 'b': 2} 
>>> d2 = {'b': 1, 'c': 3}
>>> d2.update(d1)
>>> d2
{'a': 1, 'c': 3, 'b': 2}

Mettre à jour:

Bien sûr, vous pouvez d'abord copier le dictionnaire afin d'en créer un nouveau fusionné. Cela peut être nécessaire ou non. Dans le cas où vous avez des objets composés (objets qui contiennent d'autres objets, comme des listes ou des instances de classe) dans votre dictionnaire, copy.deepcopydoivent également être pris en compte.

Félix Kling
la source
1
Dans ce cas, les éléments d1 devraient correctement avoir la priorité si des clés en conflit sont trouvées
Trey Hunner
Au cas où vous en auriez encore besoin, faites simplement une copie. d3 = d2.copy () d3.update (d1) mais j'aimerais que d1 + d2 soit ajouté à la langue.
stach
4
d1 + d2 est problématique car un dictionnaire doit avoir la priorité pendant les conflits, et il n'est pas particulièrement évident de savoir lequel.
rjh
d1 + d2 ne sera jamais implémenté que si Python gagne un multimap, sinon l'ambiguïté pour l'utilisateur est trop déroutante pour le gain de frappe de 8 octets.
Nick Bastin
Vous avez des objets dans le dictionnaire dans cet exemple: isinstance(int, object) is Truepourtant deepcopycela ne semble pas nécessaire.
Antony Hatchkins
43

En Python2,

d1={'a':1,'b':2}
d2={'a':10,'c':3}

d1 remplace d2:

dict(d2,**d1)
# {'a': 1, 'c': 3, 'b': 2}

d2 remplace d1:

dict(d1,**d2)
# {'a': 10, 'c': 3, 'b': 2}

Ce comportement n'est pas simplement un hasard de mise en œuvre; il est garanti dans la documentation :

Si une clé est spécifiée à la fois dans l'argument de position et en tant qu'argument de mot-clé, la valeur associée au mot-clé est conservée dans le dictionnaire.

unutbu
la source
3
Vos exemples échoueront (produisant un TypeError) dans Python 3.2, et dans les versions actuelles de Jython, PyPy et IronPython: pour ces versions de Python, lors du passage d'un dict avec la **notation, toutes les clés de ce dict doivent être des chaînes. Pour en savoir plus, consultez le fil de discussion python-dev à partir de mail.python.org/pipermail/python-dev/2010-April/099427.html .
Mark Dickinson
@Mark: Merci pour la mise en garde. J'ai modifié le code pour le rendre compatible avec les implémentations non CPython.
unutbu
3
il échoue si vos clés sont des tuples de chaînes et de nombres. par exemple. d1 = {(1, 'a'): 1, (1, 'b'): 0,} d2 = {(1, 'a'): 1, (2, 'b'): 2, (2, 'a'): 1,}
MySchizoBuddy
En ce qui concerne la syntaxe de décompression, consultez cet article pour les modifications à venir dans python 3.5.
Ioannis Filippidis
J'allais dire que ça d = dict(**d1, **d2)marche, mais c'est ce que @IoannisFilippidis fait référence dans son commentaire. Peut-être que l'inclusion de l'extrait ici aurait été plus claire, alors la voici.
dwanderson
14

Si vous voulez d1avoir la priorité dans les conflits, faites:

d3 = d2.copy()
d3.update(d1)

Sinon, inversez d2et d1.

tzot
la source
1

Ma solution est de définir une fonction de fusion . Ce n'est pas sophistiqué et ne coûte qu'une ligne. Voici le code en Python 3.

from functools import reduce
from operator import or_

def merge(*dicts):
    return { k: reduce(lambda d, x: x.get(k, d), dicts, None) for k in reduce(or_, map(lambda x: x.keys(), dicts), set()) }

Des tests

>>> d = {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
>>> d_letters = {0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>> merge(d, d_letters)
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>> merge(d_letters, d)
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>> merge(d)
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
>>> merge(d_letters)
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l', 12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w', 23: 'x', 24: 'y', 25: 'z', 26: 'A', 27: 'B', 28: 'C', 29: 'D', 30: 'E', 31: 'F', 32: 'G', 33: 'H', 34: 'I', 35: 'J', 36: 'K', 37: 'L', 38: 'M', 39: 'N', 40: 'O', 41: 'P', 42: 'Q', 43: 'R', 44: 'S', 45: 'T', 46: 'U', 47: 'V', 48: 'W', 49: 'X', 50: 'Y', 51: 'Z'}
>>> merge()
{}

Cela fonctionne pour un nombre arbitraire d'arguments de dictionnaire. S'il y avait des clés en double dans ces dictionnaires, la clé du dictionnaire le plus à droite dans la liste d'arguments l'emporte.

Lei Zhao
la source
1
Une simple boucle avec un .updateappel ( merged={}suivi de for d in dict: merged.update(d)) serait plus courte, plus lisible et plus efficace.
Mark Dickinson
1
Ou si vous voulez vraiment utiliser reduceand lambdas, qu'en est-il return reduce(lambda x, y: x.update(y) or x, dicts, {})?
Mark Dickinson
1
Vous pouvez essayer votre code dans le shell et voir s'il est correct. Ce que j'essayais de faire, c'est d'écrire une fonction qui peut prendre différents nombres d'arguments de dictionnaire avec la même fonctionnalité. Il vaut mieux ne pas utiliser x.update (y) sous le lambda, car il renvoie toujours None . Et j'essaie d'écrire une fonction plus générale merge_with qui prend divers nombres d'arguments de dictionnaire et traite les clés en double avec la fonction fournie. Une fois que j'ai terminé, je le posterai dans un autre fil de discussion où la solution est plus pertinente.
Lei Zhao
Voici le lien où j'ai écrit la solution la plus générale. Bienvenue et jetez un œil.
Lei Zhao
1

À partir de Python 3.9, l'opérateur |crée un nouveau dictionnaire avec les clés et les valeurs fusionnées de deux dictionnaires:

# d1 = { 'a': 1, 'b': 2 }
# d2 = { 'b': 1, 'c': 3 }
d3 = d2 | d1
# d3: {'b': 2, 'c': 3, 'a': 1}

Ce:

Crée un nouveau dictionnaire d3 avec les clés et les valeurs fusionnées de d2 et d1. Les valeurs de d1 sont prioritaires lorsque d2 et d1 partagent des clés.


Notez également l' |=opérateur qui modifie d2 en fusionnant d1 dans, avec priorité sur les valeurs d1:

# d1 = { 'a': 1, 'b': 2 }
# d2 = { 'b': 1, 'c': 3 }
d2 |= d1
# d2: {'b': 2, 'c': 3, 'a': 1}

Xavier Guihot
la source
0

Je pense que, comme indiqué ci-dessus, l'utilisation d2.update(d1)est la meilleure approche et que vous pouvez également copierd2 premier si vous en avez encore besoin.

Cependant, je tiens à souligner que dict(d1, **d2)c'est en fait une mauvaise façon de fusionner des dictionnaires en général, car les arguments de mot-clé doivent être des chaînes, donc cela échouera si vous avez un dicttel que:

{
  1: 'foo',
  2: 'bar'
}
Olivier Melançon
la source