¿Cómo fusiono dos diccionarios en una sola expresión en Python?
Quiero fusionar dos diccionarios en un diccionario nuevo.
x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
z = merge(x, y)
>>> z
{'a': 1, 'b': 3, 'c': 4}
Siempre que una clave k
esté presente en ambos diccionarios, solo y[k]
se debe conservar el valor.
¿Cómo puedo fusionar dos diccionarios de Python en una sola expresión?
Para los diccionarios x
y y
, su diccionario fusionado superficialmente z
toma valores de y
, reemplazando los de x
.
En Python 3.9.0 o superior (lanzado el 17 de octubre de 2020,
PEP-584
discutido aquí ):z = x | y
En Python 3.5 o superior:
z = {**x, **y}
En Python 2 (o 3.4 o inferior) escribe una función:
def merge_two_dicts(x, y): z = x.copy() # start with keys and values of x z.update(y) # modifies z with keys and values of y return z
y ahora:
z = merge_two_dicts(x, y)
Explicación
Supongamos que tiene dos diccionarios y desea fusionarlos en un diccionario nuevo sin alterar los diccionarios originales:
x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
El resultado deseado es obtener un nuevo diccionario ( z
) con los valores fusionados y los valores del segundo diccionario sobrescribiendo los del primero.
>>> z
{'a': 1, 'b': 3, 'c': 4}
Una nueva sintaxis para esto, propuesta en PEP 448 y disponible a partir de Python 3.5 , es
z = {**x, **y}
Y de hecho es una sola expresión.
Tenga en cuenta que también podemos fusionarnos con notación literal:
z = {**x, 'foo': 1, 'bar': 2, **y}
y ahora:
>>> z
{'a': 1, 'b': 3, 'foo': 1, 'bar': 2, 'c': 4}
Ahora se muestra como implementado en el calendario de lanzamiento de 3.5, PEP 478 , y ahora ha llegado al documento Novedades de Python 3.5 .
Sin embargo, dado que muchas organizaciones todavía utilizan Python 2, es posible que desee hacerlo de forma compatible con versiones anteriores. La forma clásica de Pythonic, disponible en Python 2 y Python 3.0-3.4, es hacer esto como un proceso de dos pasos:
z = x.copy()
z.update(y) # which returns None since it mutates z
En ambos enfoques, y
ocupará el segundo lugar y sus valores reemplazarán x
a los valores de , por lo que b
apuntarán 3
en nuestro resultado final.
Todavía no estoy en Python 3.5, pero quiero una sola expresión
Si aún no estás en Python 3.5 o necesitas escribir código compatible con versiones anteriores y quieres esto en una sola expresión , el enfoque más eficaz y correcto es ponerlo en una función:
def merge_two_dicts(x, y):
"""Given two dictionaries, merge them into a new dict as a shallow copy."""
z = x.copy()
z.update(y)
return z
y luego tienes una sola expresión:
z = merge_two_dicts(x, y)
También puedes crear una función para fusionar un número arbitrario de diccionarios, desde cero hasta un número muy grande:
def merge_dicts(*dict_args):
"""
Given any number of dictionaries, shallow copy and merge into a new dict,
precedence goes to key-value pairs in latter dictionaries.
"""
result = {}
for dictionary in dict_args:
result.update(dictionary)
return result
Esta función funcionará en Python 2 y 3 para todos los diccionarios. por ejemplo, diccionarios dados a
para g
:
z = merge_dicts(a, b, c, d, e, f, g)
y los pares clave-valor g
tendrán prioridad sobre los diccionarios a
de f
, y así sucesivamente.
Críticas de otras respuestas
No utilice lo que ve en la respuesta aceptada anteriormente:
z = dict(x.items() + y.items())
En Python 2, crea dos listas en la memoria para cada dict, crea una tercera lista en la memoria con una longitud igual a la longitud de las dos primeras juntas y luego descarta las tres listas para crear el dict. En Python 3, esto fallará porque estás sumando dos dict_items
objetos, no dos listas.
>>> 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'
y tendrías que crearlos explícitamente como listas, por ejemplo z = dict(list(x.items()) + list(y.items()))
. Esto es un desperdicio de recursos y potencia de cálculo.
De manera similar, tomar la unión de items()
en Python 3 ( viewitems()
en Python 2.7) también fallará cuando los valores sean objetos que no se pueden dividir (como listas, por ejemplo). Incluso si sus valores son hash, dado que los conjuntos están semánticamente desordenados, el comportamiento no está definido con respecto a la precedencia. Así que no hagas esto:
>>> c = dict(a.items() | b.items())
Este ejemplo demuestra lo que sucede cuando los valores no se pueden dividir en hash:
>>> x = {'a': []}
>>> y = {'b': []}
>>> dict(x.items() | y.items())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
Aquí hay un ejemplo donde y
debería tener prioridad, pero en cambio el valor de x
se conserva debido al orden arbitrario de los conjuntos:
>>> x = {'a': 2}
>>> y = {'a': 1}
>>> dict(x.items() | y.items())
{'a': 2}
Otro truco que no deberías usar:
z = dict(x, **y)
Esto utiliza el dict
constructor y es muy rápido y eficiente en memoria (incluso un poco más que nuestro proceso de dos pasos), pero a menos que sepa exactamente lo que está sucediendo aquí (es decir, el segundo dict se pasa como argumentos de palabras clave al constructor dict ), es difícil de leer, no es el uso previsto y, por lo tanto, no es Pythonic.
A continuación se muestra un ejemplo del uso que se está solucionando en django .
Los diccionarios están pensados para aceptar claves hash (por ejemplo, frozenset
s o tuplas), pero este método falla en Python 3 cuando las claves no son cadenas.
>>> c = dict(a, **b)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings
Desde la lista de correo , Guido van Rossum, el creador del idioma, escribió:
Estoy de acuerdo con declarar dict({}, **{1:3}) ilegal, ya que después de todo es un abuso del mecanismo **.
y
Aparentemente, dict(x, **y) se considera un "truco genial" para "llamar a x.update(y) y devolver x". Personalmente, lo encuentro más despreciable que genial.
Tengo entendido (así como el entendimiento del creador del idioma ) que el uso previsto dict(**y)
es crear diccionarios con fines de legibilidad, por ejemplo:
dict(a=1, b=10, c=11)
en lugar de
{'a': 1, 'b': 10, 'c': 11}
Respuesta a comentarios
A pesar de lo que dice Guido,
dict(x, **y)
está en línea con la especificación del dict, que por cierto. funciona tanto para Python 2 como para 3. El hecho de que esto solo funcione para claves de cadena es una consecuencia directa de cómo funcionan los parámetros de palabras clave y no una deficiencia de dict. Tampoco usar el operador ** en este lugar es un abuso del mecanismo, de hecho, ** fue diseñado precisamente para pasar diccionarios como palabras clave.
Nuevamente, no funciona para 3 cuando las claves no son cadenas. El contrato de llamada implícito es que los espacios de nombres toman diccionarios ordinarios, mientras que los usuarios sólo deben pasar argumentos de palabras clave que sean cadenas. Todos los demás exigibles lo hicieron cumplir. dict
rompió esta consistencia en 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}
Esta inconsistencia fue mala dadas otras implementaciones de Python (PyPy, Jython, IronPython). Por lo tanto, se solucionó en Python 3, ya que este uso podría ser un cambio importante.
Les afirmo que es una incompetencia maliciosa escribir intencionalmente código que solo funciona en una versión de un idioma o que solo funciona bajo ciertas restricciones arbitrarias.
Más comentarios:
dict(x.items() + y.items())
sigue siendo la solución más legible para Python 2. La legibilidad cuenta.
Mi respuesta: merge_two_dicts(x, y)
en realidad me parece mucho más claro, si realmente nos preocupa la legibilidad. Y no es compatible con versiones posteriores, ya que Python 2 está cada vez más obsoleto.
{**x, **y}
no parece manejar diccionarios anidados. el contenido de las claves anidadas simplemente se sobrescribe, no se fusiona [...] Terminé quemado por estas respuestas que no se fusionan de forma recursiva y me sorprendió que nadie lo mencionara. En mi interpretación de la palabra "fusionar", estas respuestas describen "actualizar un dictado con otro" y no fusionar.
Sí. Debo remitirlo nuevamente a la pregunta, que solicita una combinación superficial de dos diccionarios, con los valores del primero sobrescritos por los del segundo, en una sola expresión.
Suponiendo dos diccionarios de diccionarios, uno podría fusionarlos recursivamente en una sola función, pero debe tener cuidado de no modificar los diccionarios de ninguna de las fuentes, y la forma más segura de evitarlo es hacer una copia al asignar valores. Como las claves deben ser hash y, por lo tanto, normalmente son inmutables, no tiene sentido copiarlas:
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
Uso:
>>> x = {'a':{1:{}}, 'b': {2:{}}}
>>> y = {'b':{10:{}}, 'c': {11:{}}}
>>> dict_of_dicts_merge(x, y)
{'b': {2: {}, 10: {}}, 'a': {1: {}}, 'c': {11: {}}}
Proponer contingencias para otros tipos de valores está mucho más allá del alcance de esta pregunta, por lo que le señalaré mi respuesta a la pregunta canónica sobre una "Fusión de diccionarios de diccionarios" .
Ad-hocs menos eficaces pero correctos
Estos enfoques tienen menos rendimiento, pero proporcionarán un comportamiento correcto. Serán mucho menos eficientes que copy
y update
/o el nuevo desempaquetado porque iteran a través de cada par clave-valor en un nivel más alto de abstracción, pero respetan el orden de precedencia (los últimos diccionarios tienen precedencia)
También puedes encadenar los diccionarios manualmente dentro de un dict de comprensión :
{k: v for d in dicts for k, v in d.items()} # iteritems in Python 2.7
o en Python 2.6 (y quizás ya en 2.4 cuando se introdujeron las expresiones generadoras):
dict((k, v) for d in dicts for k, v in d.items()) # iteritems in Python 2
itertools.chain
encadenará los iteradores sobre los pares clave-valor en el orden correcto:
from itertools import chain
z = dict(chain(x.items(), y.items())) # iteritems in Python 2
Análisis de rendimiento
Sólo voy a hacer el análisis de rendimiento de los usos que se sabe que se comportan correctamente. (Independiente para que puedas copiar y pegar tú mismo).
from timeit import repeat
from itertools import chain
x = dict.fromkeys('abcdefg')
y = dict.fromkeys('efghijk')
def merge_two_dicts(x, y):
z = x.copy()
z.update(y)
return z
min(repeat(lambda: {**x, **y}))
min(repeat(lambda: merge_two_dicts(x, y)))
min(repeat(lambda: {k: v for d in (x, y) for k, v in d.items()}))
min(repeat(lambda: dict(chain(x.items(), y.items()))))
min(repeat(lambda: dict(item for d in (x, y) for item in d.items())))
En Python 3.8.1, NixOS:
>>> min(repeat(lambda: {**x, **y}))
1.0804965235292912
>>> min(repeat(lambda: merge_two_dicts(x, y)))
1.636518670246005
>>> min(repeat(lambda: {k: v for d in (x, y) for k, v in d.items()}))
3.1779992282390594
>>> min(repeat(lambda: dict(chain(x.items(), y.items()))))
2.740647904574871
>>> min(repeat(lambda: dict(item for d in (x, y) for item in d.items())))
4.266070580109954
$ uname -a
Linux nixos 4.19.113 #1-NixOS SMP Wed Mar 25 07:06:15 UTC 2020 x86_64 GNU/Linux
Recursos sobre diccionarios
- Mi explicación de la implementación del diccionario de Python , actualizada para 3.6.
- Responda sobre cómo agregar nuevas claves a un diccionario.
- Mapear dos listas en un diccionario
- Los documentos oficiales de Python sobre diccionarios.
- El diccionario aún más poderoso : charla de Brandon Rhodes en Pycon 2017
- Diccionarios modernos de Python, una confluencia de grandes ideas : charla de Raymond Hettinger en Pycon 2017
En tu caso, puedes hacer:
z = dict(list(x.items()) + list(y.items()))
Esto, como lo desea, colocará el dictado final z
y hará que el valor de la clave b
sea anulado correctamente por el y
valor del segundo dictado ():
>>> 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 usa Python 2, incluso puede eliminar las list()
llamadas. Para crear z:
>>> z = dict(x.items() + y.items())
>>> z
{'a': 1, 'c': 11, 'b': 10}
Si usa Python versión 3.9.0a4 o superior, puede usar directamente:
>>> x = {'a': 1, 'b': 2}
>>> y = {'b': 10, 'c': 11}
>>> z = x | y
>>> z
{'a': 1, 'c': 11, 'b': 10}
Una alternativa:
z = x.copy()
z.update(y)
Otra opción más concisa:
z = dict(x, **y)
Nota : esta se ha convertido en una respuesta popular, pero es importante señalar que si y
tiene claves que no sean cadenas, el hecho de que esto funcione es un abuso de un detalle de implementación de CPython, y no funciona en Python 3. o en PyPy, IronPython o Jython. Además, Guido no es fanático . Por lo tanto, no puedo recomendar esta técnica para código portátil compatible con versiones posteriores o de implementación cruzada, lo que realmente significa que debe evitarse por completo.
Probablemente esta no sea una respuesta popular, pero es casi seguro que no quieras hacerlo. Si desea una copia que sea una combinación, utilice copiar (o copia profunda , según lo que desee) y luego actualice. Las dos líneas de código son mucho más legibles (más pitónicas) que la creación de una sola línea con .items() + .items(). Lo explícito es mejor que lo implícito.
Además, cuando usas .items() (anterior a Python 3.0), estás creando una nueva lista que contiene los elementos del dict. Si sus diccionarios son grandes, entonces eso supone una gran sobrecarga (dos listas grandes que se desecharán tan pronto como se cree el dictado fusionado). update() puede funcionar de manera más eficiente, porque puede ejecutar el segundo dictado elemento por elemento.
En términos de tiempo :
>>> 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
En mi opinión, la pequeña desaceleración entre los dos primeros vale la pena por la legibilidad. Además, los argumentos de palabras clave para la creación de diccionarios solo se agregaron en Python 2.3, mientras que copy() y update() funcionarán en versiones anteriores.