¿Cómo fusiono dos diccionarios en una sola expresión (tomando unión de diccionarios)?

5840

Tengo dos diccionarios de Python y quiero escribir una sola expresión que devuelva estos dos diccionarios, fusionados (es decir, tomando la unión). El update()método sería lo que necesito, si devolviera su resultado en lugar de modificar un diccionario en el lugar.

>>> x = {'a': 1, 'b': 2}
>>> y = {'b': 10, 'c': 11}
>>> z = x.update(y)
>>> print(z)
None
>>> x
{'a': 1, 'b': 10, 'c': 11}

¿Cómo puedo obtener ese diccionario combinado final z, no x?

(Para ser más claro, el manejo de conflictos que gana el último dict.update()es lo que también estoy buscando).

0
7430

How can I merge two Python dictionaries in a single expression?

Para diccionarios xy y, se zconvierte en un diccionario combinado superficialmente con valores que yreemplazan los de x.

  • En Python 3.9.0 o superior (lanzado el 17 de octubre de 2020): PEP-584 , que se analiza aquí , se implementó y proporciona el método más simple:

    z = x | y          # NOTE: 3.9+ ONLY
    
  • 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 combinarlos 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 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 programa de lanzamiento para 3.5, PEP 478 , y ahora se ha abierto camino en el documento Novedades de 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, yvendrá en segundo lugar y sus valores reemplazarán a xlos valores, por lo que bapuntará 3en nuestro resultado final.

Todavía no en Python 3.5, pero quiero 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, aunque 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 puede 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 aa g:

z = merge_dicts(a, b, c, d, e, f, g) 

y pares de valores clave en gtendrán prioridad sobre los diccionarios aa f, y así sucesivamente.

Críticas de otras respuestas

No use 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 dictado, 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 dictado. En Python 3, esto fallará porque está agregando dos dict_itemsobjetos 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ía que crearlos explícitamente como listas, por ejemplo z = dict(list(x.items()) + list(y.items())). Esto es un desperdicio de recursos y capacidad 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 clasificar (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:

>>> 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 ydebería tener prioridad, pero en cambio el valor de xse 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 dictconstructor 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 con precisión 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.

Aquí hay un ejemplo del uso que se está remediando en django .

Los diccionarios están pensados ​​para tomar claves hash (por ejemplo, so frozensettuplas), 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 lenguaje, escribió:

I am fine with declaring dict({}, **{1:3}) illegal, since after all it is abuse of the ** mechanism.

y

Apparently dict(x, **y) is going around as "cool hack" for "call x.update(y) and return x". Personally, I find it more despicable than cool.

Es mi entendimiento (así como el entendimiento del creador del idioma ) que el uso previsto dict(**y)es para 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

Despite what Guido says, dict(x, **y) is in line with the dict specification, which btw. works for both Python 2 and 3. The fact that this only works for string keys is a direct consequence of how keyword parameters work and not a short-coming of dict. Nor is using the ** operator in this place an abuse of the mechanism, in fact, ** was designed precisely to pass dictionaries as keywords.

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 solo deben pasar argumentos de palabras clave que son cadenas. Todos los demás llamadores lo hicieron cumplir. dictrompió 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 corrigió en Python 3, ya que este uso podría ser un cambio importante.

Les digo que es una incompetencia malintencionada 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()) is still the most readable solution for Python 2. Readability counts.

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 posteriores, ya que Python 2 está cada vez más en desuso.

{**x, **y} does not seem to handle nested dictionaries. the contents of nested keys are simply overwritten, not merged [...] I ended up being burnt by these answers that do not merge recursively and I was surprised no one mentioned it. In my interpretation of the word "merging" these answers describe "updating one dict with another", and not merging.

Si. 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, suelen ser 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 les 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 son menos eficaces, pero proporcionarán un comportamiento correcto. Ellos serán mucho menos rendimiento inferior copyy updateo el nuevo desembalaje porque iterar a través de cada par clave-valor en un nivel más alto de abstracción, pero hacer respetar el orden de precedencia (este último diccionarios tienen prioridad)

También puede encadenar los diccionarios manualmente dentro de una comprensión de dict :

{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

Solo voy a hacer el análisis de rendimiento de los usos que se sabe que se comportan correctamente. (Independiente para que pueda copiar y pegar usted 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

23
  • 22
    @MohammadAzim "solo cadenas" solo se aplica a la expansión de argumentos de palabras clave en callables, no a la sintaxis de descompresión generalizada. Para demostrar que esto funciona: {**{(0, 1):2}}->{(0, 1): 2} 16 de mayo de 2019 a las 16:07
  • 14
    Esto se puede cambiar cuando se acepta PEP-0584. Se implementará un nuevo operador sindical con la siguiente sintaxis:x | y 28 feb.20 a las 16:26
  • 3
    @ cal97g sí, lo abordé en mi respuesta hace unos 10 días: stackoverflow.com/posts/26853961/revisions 28 feb.20 a las 16:38
  • 11
    Hola, la parte superior es un resumen, sí. Depende de usted. Todo sería una gran publicación de blog. Nota Py 3.4 y siguientes son EOL, 3.5 acercándose a EOL en 2020-09. 13 mar.20 a las 2:09
  • 9
    Estoy de acuerdo con el afán de dejar atrás el camino antiguo, pero a veces las personas tienen que trabajar en entornos donde solo tienen la tecnología más antigua disponible. Las personas también tienen que actualizar el código, y ver la forma antigua junto a la nueva les permite reemplazar con confianza el código antiguo con un código nuevo equivalente. Estoy abierto a sugerencias sobre la reorganización del material, pero creo que debemos conservar la información anterior. 17 de mayo de 2020 a las 15:04
1707

En tu caso, lo que puedes hacer es:

z = dict(list(x.items()) + list(y.items()))

Esto, como lo desee, pondrá el dict final zy hará que el valor de la clave bsea ​​anulado correctamente por el valor del segundo ( y) dict:

>>> 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
print(z)
{'a': 1, 'c': 11, 'b': 10}
1
  • 8
    No use esto ya que es muy ineficiente. (Vea los resultados de timeit a continuación). Puede haber sido necesario en los días de Py2 si una función contenedora no era una opción, pero esos días ya pasaron. 11 mar.20 a las 17:22
690

Una alternativa:

z = x.copy()
z.update(y)
4
  • 89
    Para aclarar por qué esto no cumple con los criterios proporcionados por la pregunta: no es una expresión única y no devuelve z. 21 de marzo de 2013 a las 13:15
  • dieciséis
    Póngalo de esta manera: si necesita poner dos líneas de comentarios que expliquen su línea de código a las personas a las que entrega su código ... ¿realmente lo ha hecho en una línea? :) Estoy totalmente de acuerdo en que Python no es bueno para esto: debería haber una forma mucho más fácil. Si bien esta respuesta es más pitónica, ¿es realmente tan explícita o clara? Updateno es una de las funciones "básicas" que la gente suele utilizar mucho.
    eric
    19/10/2017 a las 13:07
  • 6
    Bueno, si la gente insiste en convertirlo en un delineador, siempre puedes hacer (lambda z: z.update(y) or z)(x.copy()): P
    towr
    24 feb.20 a las 12:43
  • @AlexanderOh El requisito de una sola línea debería dar paso a la claridad. 18 de junio a las 15:36
387

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 ytiene claves que no son 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.

2
  • 2
    Funciona bien en Python 3 y PyPy y PyPy 3 , no puedo hablar con Jython o Iron. Dado que este patrón está explícitamente documentado (consulte el tercer formulario de constructor en esta documentación), yo diría que no es un "detalle de implementación" sino un uso intencional de funciones. 12/04/19 a las 13:10
  • 11
    @amcgregor Te perdiste la frase clave "si y tiene claves que no sean de cadena". Eso es lo que no funciona en Python3; el hecho de que funcione en CPython 2 es un detalle de implementación en el que no se puede confiar. IFF, todas sus claves están garantizadas para ser cadenas, esta es una opción totalmente compatible. 10 de mayo de 2019 a las 16:27
245

Probablemente esta no sea una respuesta popular, pero es casi seguro que no quieras hacer esto. Si desea una copia que sea una combinación, use copy (o deepcopy , según lo que desee) y luego actualice. Las dos líneas de código son mucho más legibles, más Pythonic, que la creación de una sola línea con .items () + .items (). Explícito es mejor que implícito.

Además, cuando usa .items () (anterior a Python 3.0), está creando una nueva lista que contiene los elementos del dict. Si sus diccionarios son grandes, entonces eso es bastante sobrecarga (dos listas grandes que se desecharán tan pronto como se cree el diccionario combinado). update () puede funcionar de manera más eficiente, porque puede ejecutar el segundo diccionario 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.

0
178

En una respuesta de seguimiento, preguntó sobre el desempeño relativo de estas dos alternativas:

z1 = dict(x.items() + y.items())
z2 = dict(x, **y)

En mi máquina, al menos (un x86_64 bastante común con Python 2.5.2), la alternativa z2no solo es más corta y simple, sino también significativamente más rápida. Puede verificar esto usted mismo usando el timeitmódulo que viene con Python.

Ejemplo 1: diccionarios idénticos mapeando 20 enteros consecutivos a sí mismos:

% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z1=dict(x.items() + y.items())'
100000 loops, best of 3: 5.67 usec per loop
% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z2=dict(x, **y)' 
100000 loops, best of 3: 1.53 usec per loop

z2gana por un factor de 3,5 aproximadamente. Los diferentes diccionarios parecen arrojar resultados bastante diferentes, pero z2siempre parecen salir adelante. (Si obtiene resultados inconsistentes para la misma prueba, intente pasar -rcon un número mayor que el 3 predeterminado).

Ejemplo 2: diccionarios no superpuestos que asignan 252 cadenas cortas a números enteros y viceversa:

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z1=dict(x.items() + y.items())'
1000 loops, best of 3: 260 usec per loop
% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z2=dict(x, **y)'               
10000 loops, best of 3: 26.9 usec per loop

z2 gana por un factor de 10. ¡Esa es una gran victoria en mi opinión!

Después de comparar esos dos, me pregunté si z1el bajo rendimiento podría atribuirse a la sobrecarga de construir las dos listas de elementos, lo que a su vez me llevó a preguntarme si esta variación podría funcionar mejor:

from itertools import chain
z3 = dict(chain(x.iteritems(), y.iteritems()))

Algunas pruebas rápidas, p. Ej.

% python -m timeit -s 'from itertools import chain; from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z3=dict(chain(x.iteritems(), y.iteritems()))'
10000 loops, best of 3: 66 usec per loop

me lleva a concluir que z3es algo más rápido que z1, pero no tan rápido como z2. Definitivamente no vale la pena escribir más.

A esta discusión todavía le falta algo importante, que es una comparación de rendimiento de estas alternativas con la forma "obvia" de fusionar dos listas: usar el updatemétodo. Para tratar de mantener las cosas en pie de igualdad con las expresiones, ninguna de las cuales modifica x o y, voy a hacer una copia de x en lugar de modificarla en el lugar, de la siguiente manera:

z0 = dict(x)
z0.update(y)

Un resultado típico:

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z0=dict(x); z0.update(y)'
10000 loops, best of 3: 26.9 usec per loop

En otras palabras, z0y z2parecen tener un rendimiento esencialmente idéntico. ¿Crees que esto podría ser una coincidencia? Yo no....

De hecho, iría tan lejos como para afirmar que es imposible que el código Python puro funcione mejor que esto. Y si puede hacerlo significativamente mejor en un módulo de extensión de C, imagino que la gente de Python podría estar interesada en incorporar su código (o una variación de su enfoque) en el núcleo de Python. Python se usa dicten muchos lugares; optimizar sus operaciones es un gran problema.

También puedes escribir esto como

z0 = x.copy()
z0.update(y)

como lo hace Tony, pero (como era de esperar) la diferencia de notación resulta no tener ningún efecto mensurable en el rendimiento. Use lo que le parezca adecuado. Por supuesto, tiene toda la razón al señalar que la versión de dos declaraciones es mucho más fácil de entender.

1
  • 7
    Esto no funciona en Python 3; items()no es catenable y iteritemsno existe. 16 de marzo de 2015 a las 5:50
163

En Python 3.0 ycollections.ChainMap versiones posteriores , puede usar qué grupos de varios dictados u otras asignaciones para crear una vista única y actualizable:

>>> from collections import ChainMap
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = dict(ChainMap({}, y, x))
>>> for k, v in z.items():
        print(k, '-->', v)
    
a --> 1
b --> 10
c --> 11

Actualización para Python 3.5 y posterior : puede usar el empaquetado y desempaquetado de diccionario extendido PEP 448 . Esto es rápido y sencillo:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> {**x, **y}
{'a': 1, 'b': 10, 'c': 11}

Actualización para Python 3.9 y posterior : puede usar el operador de unión PEP 584 :

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> x | y
{'a': 1, 'b': 10, 'c': 11}
5
  • 3
    Pero uno debe tener cuidado al usar ChainMap, hay un problema de que si tiene claves duplicadas, los valores del primer mapeo se usan y cuando llama dela, digamos que ChainMap c eliminará el primer mapeo de esa clave.
    Slayer
    14 de febrero de 2017 a las 5:14
  • 9
    @Prerit ¿Qué más esperarías que hiciera? Esa es la forma normal en que funcionan los espacios de nombres encadenados. Considere cómo funciona $ PATH en bash. Eliminar un ejecutable en la ruta no excluye otro ejecutable con el mismo nombre más arriba. 15 de febrero de 2017 a las 7:24
  • 3
    @Raymond Hettinger Estoy de acuerdo, solo agregué una advertencia. Es posible que la mayoría de las personas no lo sepan. :D
    Slayer
    15 de febrero de 2017 a las 15:15
  • @Prerit Podrías lanzar a dictpara evitar eso, es decir:dict(ChainMap({}, y, x)) 15 jul 2019 a las 16:30
  • La cola de edición sugerida está llena, pero alguien puso la modificación de @wjandrea en la respuesta, lo cual es incorrecto: ya no es un archivo a single, updateable view. Esta edición debe revertirse.
    Gloweye
    19 jul a las 12:08
141

Quería algo similar, pero con la capacidad de especificar cómo se fusionaban los valores en las claves duplicadas, así que pirateé esto (pero no lo probé en gran medida). Obviamente, esta no es una expresión única, pero es una llamada de función única.

def merge(d1, d2, merge_fn=lambda x,y:y):
    """
    Merges two dictionaries, non-destructively, combining 
    values on duplicate keys as defined by the optional merge
    function.  The default behavior replaces the values in d1
    with corresponding values in d2.  (There is no other generally
    applicable merge strategy, but often you'll have homogeneous 
    types in your dicts, so specifying a merge technique can be 
    valuable.)

    Examples:

    >>> d1
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1)
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1, lambda x,y: x+y)
    {'a': 2, 'c': 6, 'b': 4}

    """
    result = dict(d1)
    for k,v in d2.iteritems():
        if k in result:
            result[k] = merge_fn(result[k], v)
        else:
            result[k] = v
    return result
1
  • Solución útil cuando no se desea el comportamiento predeterminado de las soluciones más cortas y simples (reemplazo de valores de claves comunes por el segundo diccionario). Para Python 3, iteritems () ya no está disponible en dictados, y uno puede simplemente usar items () en su lugar. 5 de marzo a las 14:22
112

Actualización recursiva / profunda de un dictado

def deepupdate(original, update):
    """
    Recursively update a dict.
    Subdict's won't be overwritten but also updated.
    """
    for key, value in original.iteritems(): 
        if key not in update:
            update[key] = value
        elif isinstance(value, dict):
            deepupdate(value, update[key]) 
    return update

Demostración:

pluto_original = {
    'name': 'Pluto',
    'details': {
        'tail': True,
        'color': 'orange'
    }
}

pluto_update = {
    'name': 'Pluutoo',
    'details': {
        'color': 'blue'
    }
}

print deepupdate(pluto_original, pluto_update)

Salidas:

{
    'name': 'Pluutoo',
    'details': {
        'color': 'blue',
        'tail': True
    }
}

Gracias rednaw por las ediciones.

2
  • 2
    Esto no responde a la pregunta. La pregunta claramente pide un nuevo diccionario, z, de los diccionarios originales, xey, con valores de y reemplazando los de x, no un diccionario actualizado. Esta respuesta modifica y en el lugar agregando valores de x. Peor aún, no copia estos valores, por lo que uno podría modificar más el diccionario modificado, y, y las modificaciones podrían reflejarse en el diccionario x. @ Jérôme Espero que este código no esté causando ningún error en su aplicación; al menos considere usar deepcopy para copiar los valores. 9 de nov. De 2018 a las 2:14
  • 2
    @AaronHall estuvo de acuerdo en que esto no responde a la pregunta. Pero responde a mi necesidad. Entiendo esas limitaciones, pero eso no es un problema en mi caso. Pensando en ello, tal vez el nombre sea engañoso, ya que podría evocar una copia profunda, que no proporciona. Pero se ocupa de la anidación profunda. Aquí hay otra implementación de Martellibot: stackoverflow.com/questions/3232943/… . 9/11/18 a las 13:24
89

Python 3.5 (PEP 448) permite una mejor opción de sintaxis:

x = {'a': 1, 'b': 1}
y = {'a': 2, 'c': 2}
final = {**x, **y} 
final
# {'a': 2, 'b': 1, 'c': 2}

O incluso

final = {'a': 1, 'b': 1, **x, **y}

En Python 3.9 también usas | y | = con el siguiente ejemplo de PEP 584

d = {'spam': 1, 'eggs': 2, 'cheese': 3}
e = {'cheese': 'cheddar', 'aardvark': 'Ethel'}
d | e
# {'spam': 1, 'eggs': 2, 'cheese': 'cheddar', 'aardvark': 'Ethel'}
2
  • ¿De qué manera esta solución es mejor que la dict(x, **y)solución? Como usted (@CarlMeyer) mencionó en la nota de su propia respuesta ( stackoverflow.com/a/39858/2798610 ), Guido considera que esa solución es ilegal . 4 de marzo de 2015 a las 11:09
  • dieciséis
    A Guido no le gusta dict(x, **y)por la (muy buena) razón de que se basa ysolo en tener claves que sean nombres de argumentos de palabra clave válidos (a menos que esté usando CPython 2.7, donde el constructor dict hace trampa). Esta objeción / restricción no se aplica a PEP 448, que generaliza la **sintaxis de descompresión para dictar literales. Entonces, esta solución tiene la misma concisión que dict(x, **y), sin la desventaja. 4 mar.15 a las 22:24
88

La mejor versión que podría pensar sin usar la copia sería:

from itertools import chain
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
dict(chain(x.iteritems(), y.iteritems()))

Es más rápido que, dict(x.items() + y.items())pero no tan rápido n = copy(a); n.update(b), al menos en CPython. Esta versión también funciona en Python 3 si cambia iteritems()a items(), lo que lo hace automáticamente la herramienta 2to3.

Personalmente, me gusta más esta versión porque describe bastante bien lo que quiero en una sola sintaxis funcional. El único problema menor es que no hace completamente obvio que los valores de y tienen prioridad sobre los valores de x, pero no creo que sea difícil de resolver.

79
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = dict(x.items() + y.items())
print z

Para los elementos con claves en ambos diccionarios ('b'), puede controlar cuál termina en la salida colocando ese último.

1
  • 1
    En python 3 obtendría TypeError: tipo (s) de operando no admitido para +: 'dict_items' y 'dict_items' ... debe encapsular cada dictado con list () como: dict (list (x.items ()) + list (y.items ())) 26 de abril de 2019 a las 8:45
63

Si bien la pregunta ya ha sido respondida varias veces, aún no se ha incluido esta sencilla solución al problema.

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z4 = {}
z4.update(x)
z4.update(y)

Es tan rápido como z0 y el z2 maligno mencionado anteriormente, pero fácil de entender y cambiar.

5
  • 3
    pero son tres declaraciones en lugar de una expresión
    fortran
    18/10/11 a las 15:44
  • dieciséis
    ¡Sí! Las soluciones de una sola expresión mencionadas son lentas o malvadas. El buen código es legible y fácil de mantener. Entonces, el problema es la pregunta, no la respuesta. Deberíamos pedir la mejor solución a un problema, no una solución de una sola línea.
    phobie
    28/10/11 a las 3:36
  • 10
    Pierda el z4 = {}y cambie la siguiente línea a z4 = x.copy(): mejor que un buen código no hace cosas innecesarias (lo que lo hace aún más legible y fácil de mantener). 8 de marzo de 2013 a las 15:10
  • 3
    Su sugerencia cambiaría esto a la respuesta de Matthews. Si bien su respuesta está bien, creo que la mía es más legible y se puede mantener mejor. La línea adicional solo sería mala si costara tiempo de ejecución.
    phobie
    6 de mayo de 2013 a las 11:50
  • Te sugiero que pongas esto en una función.
    Corman
    14 de junio de 2020 a las 23:16
58
def dict_merge(a, b):
  c = a.copy()
  c.update(b)
  return c

new = dict_merge(old, extras)

Entre respuestas tan turbias y dudosas, este brillante ejemplo es la única buena manera de fusionar dictados en Python, respaldado por el dictador de por vida Guido van Rossum . Alguien más sugirió la mitad de esto, pero no lo puso en función.

print dict_merge(
      {'color':'red', 'model':'Mini'},
      {'model':'Ferrari', 'owner':'Carl'})

da:

{'color': 'red', 'owner': 'Carl', 'model': 'Ferrari'}
52

Si crees que las lambdas son malas, no sigas leyendo. Según lo solicitado, puede escribir la solución rápida y eficiente en memoria con una expresión:

x = {'a':1, 'b':2}
y = {'b':10, 'c':11}
z = (lambda a, b: (lambda a_copy: a_copy.update(b) or a_copy)(a.copy()))(x, y)
print z
{'a': 1, 'c': 11, 'b': 10}
print x
{'a': 1, 'b': 2}

Como se sugirió anteriormente, usar dos líneas o escribir una función es probablemente una mejor manera de hacerlo.

46

Sea pitónico. Utilice una comprensión :

z={i:d[i] for d in [x,y] for i in d}

>>> print z
{'a': 1, 'c': 11, 'b': 10}
2
  • 1
    Como una función: def dictmerge(*args): return {i:d[i] for d in args for i in d} 6/06/18 a las 18:27
  • 3
    Guarde una búsqueda iterando directamente los pares clave / valor: z={k: v for d in (x, y) for k, v in d.items()} 5 mar 19 a las 19:33
41

En python3, el itemsmétodo ya no devuelve una lista , sino una vista , que actúa como un conjunto. En este caso, deberá tomar la unión de conjuntos, ya que concatenar con +no funcionará:

dict(x.items() | y.items())

Para un comportamiento similar a python3 en la versión 2.7, el viewitemsmétodo debería funcionar en lugar de items:

dict(x.viewitems() | y.viewitems())

Prefiero esta notación de todos modos, ya que parece más natural pensar en ella como una operación de unión de conjuntos en lugar de una concatenación (como muestra el título).

Editar:

Un par de puntos más para Python 3. Primero, tenga en cuenta que el dict(x, **y)truco no funcionará en Python 3 a menos que las claves ysean cadenas.

Además, la respuesta de Chainmap de Raymond Hettinger es bastante elegante, ya que puede tomar una cantidad arbitraria de dictados como argumentos, pero de los documentos parece que revisa secuencialmente una lista de todos los dictados para cada búsqueda:

Lookups search the underlying mappings successively until a key is found.

Esto puede ralentizarlo si tiene muchas búsquedas en su aplicación:

In [1]: from collections import ChainMap
In [2]: from string import ascii_uppercase as up, ascii_lowercase as lo; x = dict(zip(lo, up)); y = dict(zip(up, lo))
In [3]: chainmap_dict = ChainMap(y, x)
In [4]: union_dict = dict(x.items() | y.items())
In [5]: timeit for k in union_dict: union_dict[k]
100000 loops, best of 3: 2.15 µs per loop
In [6]: timeit for k in chainmap_dict: chainmap_dict[k]
10000 loops, best of 3: 27.1 µs per loop

Entonces, aproximadamente un orden de magnitud más lento para las búsquedas. Soy fanático de Chainmap, pero parece menos práctico donde puede haber muchas búsquedas.

0
32

Dos diccionarios

def union2(dict1, dict2):
    return dict(list(dict1.items()) + list(dict2.items()))

n diccionarios

def union(*dicts):
    return dict(itertools.chain.from_iterable(dct.items() for dct in dicts))

sumtiene mal desempeño. Ver https://mathieularose.com/how-not-to-flatten-a-list-of-lists-in-python/

0
31

Solución simple usando itertools que preserva el orden (los últimos dictados tienen prioridad)

# py2
from itertools import chain, imap
merge = lambda *args: dict(chain.from_iterable(imap(dict.iteritems, args)))

# py3
from itertools import chain
merge = lambda *args: dict(chain.from_iterable(map(dict.items, args)))

Y su uso:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> merge(x, y)
{'a': 1, 'b': 10, 'c': 11}

>>> z = {'c': 3, 'd': 4}
>>> merge(x, y, z)
{'a': 1, 'b': 10, 'c': 3, 'd': 4}
0
29

Abuso que conduce a una solución de una sola expresión para la respuesta de Matthew :

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (lambda f=x.copy(): (f.update(y), f)[1])()
>>> z
{'a': 1, 'c': 11, 'b': 10}

Dijiste que querías una expresión, así que abusé lambdade vincular un nombre y tuplas para anular el límite de una expresión de lambda. Siéntete libre de encogerte.

También puede hacer esto, por supuesto, si no le importa copiarlo:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (x.update(y), x)[1]
>>> z
{'a': 1, 'b': 10, 'c': 11}
0
24

Aunque las respuestas fueron buenas para este diccionario superficial , ninguno de los métodos definidos aquí en realidad hace una combinación profunda de diccionario.

A continuación se muestran algunos ejemplos:

a = { 'one': { 'depth_2': True }, 'two': True }
b = { 'one': { 'extra': False } }
print dict(a.items() + b.items())

Uno esperaría un resultado de algo como esto:

{ 'one': { 'extra': False', 'depth_2': True }, 'two': True }

En cambio, obtenemos esto:

{'two': True, 'one': {'extra': False}}

La entrada 'one' debería haber tenido 'depth_2' y 'extra' como elementos dentro de su diccionario si realmente se trataba de una combinación.

Usar cadena también, no funciona:

from itertools import chain
print dict(chain(a.iteritems(), b.iteritems()))

Resultados en:

{'two': True, 'one': {'extra': False}}

La fusión profunda que dio rcwesick también crea el mismo resultado.

Sí, funcionará fusionar los diccionarios de muestra, pero ninguno de ellos es un mecanismo genérico para fusionar. Actualizaré esto más tarde una vez que escriba un método que haga una verdadera fusión.

0
22

Si no te importa mutar x,

x.update(y) or x

Sencillo, legible y eficaz. Usted sabe update() siempre vuelve None, que es un valor falso. Por lo tanto, la expresión anterior siempre se evaluará a x, después de actualizarla.

La mayoría de los métodos mutantes en la biblioteca estándar (como .update()) regresan Nonepor convención, por lo que este tipo de patrón también funcionará en ellos. Sin embargo, si está utilizando una subclase dict o algún otro método que no sigue esta convención, entonces orpuede devolver su operando izquierdo, que puede no ser lo que desea. En su lugar, puede usar una visualización e índice de tuplas, que funcionan independientemente de lo que evalúe el primer elemento (aunque no es tan bonito):

(x.update(y), x)[-1]

Si aún no tiene xuna variable, puede usarla lambdapara hacer un local sin usar una instrucción de asignación. Esto equivale a usar lambdacomo una expresión let , que es una técnica común en los lenguajes funcionales, pero tal vez no sea pitónica.

(lambda x: x.update(y) or x)({'a': 1, 'b': 2})

Aunque no es tan diferente del siguiente uso del nuevo operador de morsa (solo Python 3.8+),

(x := {'a': 1, 'b': 2}).update(y) or x

especialmente si usa un argumento predeterminado:

(lambda x={'a': 1, 'b': 2}: x.update(y) or x)()

Si desea una copia, el estilo PEP 584x | y es el más Pythonic en 3.9+. Si debe admitir versiones anteriores, el estilo PEP 448{**x, **y} es más fácil para 3.5+. Pero si eso no está disponible en su versión de Python (incluso más antigua), el patrón de expresión let también funciona aquí.

(lambda z=x.copy(): z.update(y) or z)()

(Eso es, por supuesto, casi equivalente a (z := x.copy()).update(y) or z, pero si su versión de Python es lo suficientemente nueva para eso, entonces el estilo PEP 448 estará disponible).

18

Nuevo en Python 3.9: use el operador de unión (|) para fusionardicts similares asets:

>>> d = {'a': 1, 'b': 2}
>>> e = {'a': 9, 'c': 3}
>>> d | e
{'a': 9, 'b': 2, 'c': 3}

Para claves coincidentes, la derecha dicttiene prioridad .

Esto también funciona para |=modificar un dictsitio:

>>> e |= d    # e = e | d
>>> e
{'a': 1, 'c': 3, 'b': 2}
17

Es tan tonto que .updateno devuelve nada.
Solo uso una función de ayuda simple para resolver el problema:

def merge(dict1,*dicts):
    for dict2 in dicts:
        dict1.update(dict2)
    return dict1

Ejemplos:

merge(dict1,dict2)
merge(dict1,dict2,dict3)
merge(dict1,dict2,dict3,dict4)
merge({},dict1,dict2)  # this one returns a new copy
17

(Solo para Python2.7 *; existen soluciones más simples para Python3 *.)

Si no es reacio a importar un módulo de biblioteca estándar, puede hacerlo

from functools import reduce

def merge_dicts(*dicts):
    return reduce(lambda a, d: a.update(d) or a, dicts, {})

(El or abit en el lambdaes necesario porque dict.updatesiempre regresa Nonecon éxito).

0
16

Basándome en ideas aquí y en otros lugares, he comprendido una función:

def merge(*dicts, **kv): 
      return { k:v for d in list(dicts) + [kv] for k,v in d.items() }

Uso (probado en python 3):

assert (merge({1:11,'a':'aaa'},{1:99, 'b':'bbb'},foo='bar')==\
    {1: 99, 'foo': 'bar', 'b': 'bbb', 'a': 'aaa'})

assert (merge(foo='bar')=={'foo': 'bar'})

assert (merge({1:11},{1:99},foo='bar',baz='quux')==\
    {1: 99, 'foo': 'bar', 'baz':'quux'})

assert (merge({1:11},{1:99})=={1: 99})

En su lugar, podrías usar una lambda.

16

El problema que tengo con las soluciones enumeradas hasta la fecha es que, en el diccionario combinado, el valor de la clave "b" es 10 pero, en mi opinión, debería ser 12. En ese sentido, presento lo siguiente:

import timeit

n=100000
su = """
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
"""

def timeMerge(f,su,niter):
    print "{:4f} sec for: {:30s}".format(timeit.Timer(f,setup=su).timeit(n),f)

timeMerge("dict(x, **y)",su,n)
timeMerge("x.update(y)",su,n)
timeMerge("dict(x.items() + y.items())",su,n)
timeMerge("for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k] ",su,n)

#confirm for loop adds b entries together
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]
print "confirm b elements are added:",x

Resultados:

0.049465 sec for: dict(x, **y)
0.033729 sec for: x.update(y)                   
0.150380 sec for: dict(x.items() + y.items())   
0.083120 sec for: for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]

confirm b elements are added: {'a': 1, 'c': 11, 'b': 12}
1
15
from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))

Esto debería solucionar tu problema.

1
  • Recomendaré usar el contador en .update()lugar de +. Esto se debe a que, si la suma da como resultado un valor de 0 para cualquiera de las claves, Counter la eliminará. 7 de agosto a las 18:23
15

Habrá una nueva opción cuando se lance Python 3.8 ( programado para el 20 de octubre de 2019 ), gracias a PEP 572: Assignment Expressions . El nuevo operador de expresión de asignación le :=permite asignar el resultado de copyy aún usarlo para llamar update, dejando el código combinado como una sola expresión, en lugar de dos declaraciones, cambiando:

newdict = dict1.copy()
newdict.update(dict2)

para:

(newdict := dict1.copy()).update(dict2)

mientras se comporta de manera idéntica en todos los sentidos. Si también debe devolver el resultado dict(solicitó una expresión que devuelva el dict; lo anterior crea y asigna a newdict, pero no lo devuelve, por lo que no podría usarlo para pasar un argumento a una función como está, a la myfunc((newdict := dict1.copy()).update(dict2))) , luego simplemente agregue or newdictal final (dado que updatedevuelve None, que es falso, luego evaluará y devolverá newdictcomo resultado de la expresión):

(newdict := dict1.copy()).update(dict2) or newdict

Advertencia importante: en general, desaconsejaría este enfoque a favor de:

newdict = {**dict1, **dict2}

El enfoque de desempaquetado es más claro (para cualquiera que sepa sobre desempaquetado generalizado en primer lugar, lo cual debería ), no requiere un nombre para el resultado en absoluto (por lo que es mucho más conciso cuando se construye un temporal que se pasa inmediatamente a un función o incluido en un list/ tupleliteral o similar), y es casi seguro que también es más rápido, siendo (en CPython) aproximadamente equivalente a:

newdict = {}
newdict.update(dict1)
newdict.update(dict2)

pero se hace en la capa C, usando la dictAPI concreta , por lo que no se involucra la búsqueda / enlace de método dinámico o la sobrecarga de despacho de llamadas de función (donde (newdict := dict1.copy()).update(dict2)es inevitablemente idéntica al comportamiento original de dos líneas, realizando el trabajo en pasos discretos, con búsqueda dinámica / vinculación / invocación de métodos.

También es más extensible, ya que fusionar tres dicts es obvio:

 newdict = {**dict1, **dict2, **dict3}

donde el uso de expresiones de asignación no escalará así; lo más cercano que podría obtener sería:

 (newdict := dict1.copy()).update(dict2), newdict.update(dict3)

o sin la tupla temporal de Nones, pero con prueba de veracidad de cada Noneresultado:

 (newdict := dict1.copy()).update(dict2) or newdict.update(dict3)

cualquiera de los cuales es obviamente mucho más feo e incluye más ineficiencias (ya sea un desperdicio temporal tuplede Nones para la separación de comas, o una prueba de veracidad sin sentido de updatela Nonedevolución de cada uno para la orseparación).

La única ventaja real del enfoque de expresión de asignación ocurre si:

  1. Tiene un código genérico que necesita manejar sets y dicts (ambos admiten copyy update, por lo que el código funciona aproximadamente como lo esperaría)
  2. Espera recibir objetos arbitrarios similares a dict , no solo a dictsí mismo, y debe preservar el tipo y la semántica del lado izquierdo (en lugar de terminar con un simple dict). Si bien myspecialdict({**speciala, **specialb})podría funcionar, implicaría un temporal adicional dict, y si myspecialdicttiene características dictque no se pueden preservar (por ejemplo, los correos electrónicos regulares dictahora conservan el orden en función de la primera aparición de una clave y el valor en función de la última aparición de una clave; es posible que desee uno que conserva el orden basado en el últimoaparición de una clave, por lo que actualizar un valor también lo mueve al final), entonces la semántica sería incorrecta. Dado que la versión de la expresión de asignación usa los métodos nombrados (que presumiblemente están sobrecargados para comportarse apropiadamente), nunca crea un dicten absoluto (a menos que dict1ya fuera a dict), preservando el tipo original (y la semántica del tipo original), todo mientras evita los temporales.
12

Esto se puede hacer con una sola comprensión de dictado:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> { key: y[key] if key in y else x[key]
      for key in set(x) + set(y)
    }

En mi opinión, la mejor respuesta para la parte de 'expresión única' ya que no se necesitan funciones adicionales y es corta.

2
  • Sin embargo, sospecho que el rendimiento no será muy bueno; crear un conjunto de cada dictado y luego solo iterar a través de las teclas significa otra búsqueda del valor cada vez (aunque relativamente rápido, aún aumenta el orden de la función para escalar)
    Breezer
    16/02/2017 a las 14:57
  • 3
    todo depende de la versión de Python que estemos usando. En 3.5 y superior {** x, ** y} da el diccionario concatenado 23 dic 2017 a las 15:50