¿Cómo fusiono diccionarios en Python?

91
d3 = dict(d1, **d2)

Entiendo que esto fusiona el diccionario. Pero, ¿es único? ¿Qué pasa si d1 tiene la misma clave que d2 pero un valor diferente? Me gustaría que d1 y d2 se fusionen, pero d1 tiene prioridad si hay una clave duplicada.

TIMEX
fuente
9
Tenga en cuenta que este truco se considera un abuso del **paso de argumentos de palabras clave, a menos que todas las claves de d2sean cadenas. Si no todas las claves de d2son cadenas, esto falla en Python 3.2 y en implementaciones alternativas de Python como Jython, IronPython y PyPy. Consulte, por ejemplo, mail.python.org/pipermail/python-dev/2010-April/099459.html .
Mark Dickinson

Respuestas:

154

Puede utilizar el .update()método si ya no necesita el original d2:

Actualice el diccionario con los pares clave / valor de otros, sobrescribiendo las claves existentes . Regreso None.

P.ej:

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

Actualizar:

Por supuesto, puede copiar el diccionario primero para crear uno nuevo combinado. Esto puede ser necesario o no. En caso de que tenga objetos compuestos (objetos que contienen otros objetos, como listas o instancias de clase) en su diccionario, copy.deepcopytambién debe considerarse.

Felix Kling
fuente
1
En este caso, los elementos d1 deberían tener prioridad correctamente si se encuentran claves en conflicto
Trey Hunner
En caso de que aún lo necesite, simplemente haga una copia. d3 = d2.copy () d3.update (d1) pero me gustaría que d1 + d2 se agregue al idioma.
2010
4
d1 + d2 es problemático porque un diccionario debe tener prioridad durante los conflictos y no es particularmente obvio cuál.
rjh
d1 + d2 solo se implementará si Python obtiene un multimapa; de lo contrario, la ambigüedad para el usuario es demasiado confusa para la ganancia de escritura de 8 bytes.
Nick Bastin
Tiene objetos en el diccionario en este ejemplo: isinstance(int, object) is Truepero deepcopyno parece necesario.
Antony Hatchkins
43

En Python2,

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

d1 anula d2:

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

d2 anula d1:

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

Este comportamiento no es solo una casualidad de implementación; está garantizado en la documentación :

Si se especifica una clave tanto en el argumento posicional como en el argumento de palabra clave, el valor asociado con la palabra clave se conserva en el diccionario.

unutbu
fuente
3
Sus ejemplos fallarán (produciendo un TypeError) en Python 3.2, y en las versiones actuales de Jython, PyPy e IronPython: para esas versiones de Python, al pasar un dictado con la **notación, todas las claves de ese dictado deben ser cadenas. Consulte el hilo de python-dev que comienza en mail.python.org/pipermail/python-dev/2010-April/099427.html para obtener más información.
Mark Dickinson
@Mark: Gracias por avisar. Edité el código para hacerlo compatible con implementaciones que no son de CPython.
unutbu
3
falla si sus claves son tuplas de cadenas y números. por ej. d1 = {(1, 'a'): 1, (1, 'b'): 0,} d2 = {(1, 'a'): 1, (2, 'b'): 2, (2, 'a'): 1,}
MySchizoBuddy
Con respecto a la sintaxis de desempaquetado, consulte esta publicación para conocer los cambios que se realizarán en python 3.5.
Ioannis Filippidis
Iba a decir que d = dict(**d1, **d2)funciona, pero eso es lo que @IoannisFilippidis hace referencia en su comentario. Quizás incluir el fragmento aquí hubiera sido más claro, así que aquí está.
dwanderson
14

Si quiere d1tener prioridad en los conflictos, haga:

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

De lo contrario, invierta d2y d1.

tzot
fuente
1

Mi solución es definir una función de combinación . No es sofisticado y solo cuesta una línea. Aquí está el código 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()) }

Pruebas

>>> 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()
{}

Funciona para un número arbitrario de argumentos de diccionario. Si hubiera claves duplicadas en ese diccionario, gana la clave del diccionario situado más a la derecha en la lista de argumentos.

Lei Zhao
fuente
1
Un bucle simple con una .updatellamada en él ( merged={}seguido de for d in dict: merged.update(d)) sería más corto, más legible y más eficiente.
Mark Dickinson
1
O si realmente desea utilizar reducey lambdaS, ¿qué tal return reduce(lambda x, y: x.update(y) or x, dicts, {})?
Mark Dickinson
1
Puede probar su código en el shell y ver si es correcto. Lo que estaba tratando de hacer es escribir una función que pueda tomar varios argumentos de diccionario con la misma funcionalidad. Es mejor no usar x.update (y) debajo de lambda, porque siempre devuelve None . Y estoy tratando de escribir una función más general merge_with que tome varios argumentos de diccionario y trate con claves duplicadas con la función proporcionada. Una vez que termine, lo publicaré en otro hilo donde la solución sea más relevante.
Lei Zhao
Aquí está el enlace donde escribí la solución más general. Bienvenido y echa un vistazo.
Lei Zhao
1

Comenzando Python 3.9, el operador |crea un nuevo diccionario con las claves y valores combinados de dos diccionarios:

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

Esta:

Crea un nuevo diccionario d3 con las claves y valores combinados de d2 y d1. Los valores de d1 tienen prioridad cuando d2 y d1 comparten claves.


También tenga en cuenta el |=operador que modifica d2 fusionando d1 en, con prioridad en los valores de d1:

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

Xavier Guihot
fuente
0

Creo que, como se indicó anteriormente, usar d2.update(d1)es el mejor enfoque y que también puede copiar d2primero si aún lo necesita.

Aunque, quiero señalar que en dict(d1, **d2)realidad es una mala manera de fusionar diccionarios en general, ya que los argumentos de palabras clave deben ser cadenas, por lo que fallará si tiene una dictcomo:

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