¿Cómo puedo fusionar dos diccionarios de Python en una sola expresión?
Para los diccionarios x
y y
, se z
convierte en un diccionario poco fusionado con valores que y
reemplazan a los de x
.
En Python 3.5 o superior:
z = {**x, **y}
En Python 2, (o 3.4 o inferior) escriba una función:
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
y ahora:
z = merge_two_dicts(x, y)
En Python 3.9.0a4 o superior (fecha de lanzamiento final aproximadamente en octubre de 2020): PEP-584 , discutido aquí , se implementó para simplificar aún más esto:
z = x | y # NOTE: 3.9+ ONLY
Explicación
Digamos que tiene dos dictados y desea fusionarlos en un nuevo dict sin alterar los dictados originales:
x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
El resultado deseado es obtener un nuevo diccionario ( z
) con los valores combinados, y los valores del segundo dict 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 la 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 cronograma de lanzamiento de 3.5, PEP 478 , y ahora ha llegado al documento Novedades en Python 3.5 .
Sin embargo, dado que muchas organizaciones todavía están en Python 2, es posible que desee hacer esto de una manera 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
vendrá en segundo lugar y sus valores reemplazarán a x
los valores, por 'b'
lo tanto, apuntarán a 3
nuestro resultado final.
Todavía no en Python 3.5, pero quiere una sola expresión
Si aún no está en Python 3.5, o necesita escribir código compatible con versiones anteriores, y desea 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 dicts, 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 puede hacer una función para fusionar un número indeterminado de dictados, de cero a un número muy grande:
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
Esta función funcionará en Python 2 y 3 para todos los dictados. ej. dados dados a
a g
:
z = merge_dicts(a, b, c, d, e, f, g)
y los pares de valores clave g
tendrán prioridad sobre los dictados a
a f
, y así sucesivamente.
Críticas de otras respuestas
No use lo que ve en la respuesta anteriormente aceptada:
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á agregando dos dict_items
objetos juntos, 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 poder de cálculo.
Del mismo modo, tomar la unión de items()
Python 3 ( viewitems()
en Python 2.7) también fallará cuando los valores sean objetos no compartibles (como listas, por ejemplo). Incluso si sus valores son hashable, ya que los conjuntos están semánticamente desordenados, el comportamiento no está definido con respecto a la precedencia. Entonces no hagas esto:
>>> c = dict(a.items() | b.items())
Este ejemplo demuestra lo que sucede cuando los valores no son compartibles:
>>> 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 retiene debido al orden arbitrario de los conjuntos:
>>> x = {'a': 2}
>>> y = {'a': 1}
>>> dict(x.items() | y.items())
{'a': 2}
Otro truco que no debes usar:
z = dict(x, **y)
Esto usa el dict
constructor, y es muy rápido y eficiente en la 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 dict constructor), es difícil de leer, no es el uso previsto, por lo que no es Pythonic.
Aquí hay un ejemplo del uso que se está remediando en django .
Los dictos están destinados a tomar claves hashables (por ejemplo, congelados 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
De la lista de correo , Guido van Rossum, el creador del lenguaje, escribió:
Estoy de acuerdo con declarar ilegal dict ({}, ** {1: 3}), ya que después de todo es abuso del mecanismo **.
y
Aparentemente, dict (x, ** y) aparece como "truco genial" para "llamar a x.update (y) y devolver x". Personalmente, me parece más despreciable que genial.
Es mi entendimiento (así como el entendimiento del creador del lenguaje ) que el uso previsto dict(**y)
es para crear dictados con fines de legibilidad, por ejemplo:
dict(a=1, b=10, c=11)
en vez 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 dict, que por cierto. funciona tanto para Python 2 como para 3. El hecho de que esto solo funcione para las claves de cadena es una consecuencia directa de cómo funcionan los parámetros de palabras clave y no un corto de dict. Tampoco el uso del operador ** en este lugar es un abuso del mecanismo, de hecho ** fue diseñado precisamente para aprobar dictados como palabras clave.
Nuevamente, no funciona para 3 cuando las teclas no son cadenas. El contrato de llamada implícito es que los espacios de nombres toman dictados ordinarios, mientras que los usuarios solo deben pasar argumentos de palabras clave que son cadenas. Todas las otras callables 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 dada otras implementaciones de Python (Pypy, Jython, IronPython). Por lo tanto, se solucionó en Python 3, ya que este uso podría ser un cambio radical.
Le presento que es una incompetencia maliciosa escribir código intencionalmente que solo funciona en una versión de un idioma o que solo funciona dadas 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: en merge_two_dicts(x, y)
realidad me parece mucho más claro, si realmente nos preocupa la legibilidad. Y no es compatible con versiones anteriores, 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 recursivamente y me sorprendió que nadie lo mencionara. En mi interpretación de la palabra "fusionar" estas respuestas describen "actualizar un dict con otro", y no fusionar.
Si. Debo remitirlo nuevamente a la pregunta, que es solicitar una fusión superficial de dos diccionarios, con los valores del primero sobrescritos por los segundos, 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 dictados de ninguna de las fuentes, y la forma más segura de evitar eso es hacer una copia al asignar valores. Como las claves deben ser hashable y, por lo tanto, generalmente 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 diré mi respuesta a la pregunta canónica sobre una "Fusión de diccionarios de diccionarios" .
Ad-hocs menos efectivos pero correctos
Estos enfoques son menos efectivos, pero proporcionarán un comportamiento correcto. Tendrán mucho menos rendimiento 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 lo hacen respetar el orden de precedencia (este último predice tienen prioridad)
También puede encadenar los dictados manualmente dentro de una comprensión dict:
{k: v for d in dicts for k, v in d.items()} # iteritems in Python 2.7
o en python 2.6 (y tal vez tan pronto como 2.4 cuando se introdujeron las expresiones generadoras):
dict((k, v) for d in dicts for k, v in d.items())
itertools.chain
encadenará los iteradores sobre los pares clave-valor en el orden correcto:
import itertools
z = dict(itertools.chain(x.iteritems(), y.iteritems()))
Análisis de rendimiento
Solo voy a hacer el análisis de rendimiento de los usos que se sabe que se comportan correctamente.
import timeit
Lo siguiente se hace en Ubuntu 14.04
En Python 2.7 (sistema 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 de DenaSnakes):
>>> 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
Recursos sobre diccionarios
z = x | y