¿Cómo fusiono dos diccionarios en una sola expresión en Python?

4789

Tengo dos diccionarios Python, y quiero escribir una sola expresión que devuelva estos dos diccionarios, combinados. El update()método sería lo que necesito, si devuelve 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 del conflicto de último ganador dict.update()es lo que también estoy buscando).

Carl Meyer
fuente
1
Si está usando Python 3.9 alpha, simplemente usez = x | y
The Daleks

Respuestas:

5708

¿Cómo puedo fusionar dos diccionarios de Python en una sola expresión?

Para los diccionarios xy y, se zconvierte en un diccionario poco fusionado con valores que yreemplazan 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, yvendrá en segundo lugar y sus valores reemplazarán a xlos valores, por 'b'lo tanto, apuntarán a 3nuestro 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 aa g:

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

y los pares de valores clave gtendrán prioridad sobre los dictados aa 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_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í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. 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 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 copyy / updateo 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

Aaron Hall
fuente
99
@MohammadAzim "solo cadenas" solo se aplica a la expansión de argumentos de palabras clave en invocables, no a la sintaxis general de desempaquetado. Para demostrar que esto funciona: {**{(0, 1):2}}->{(0, 1): 2}
Aaron Hall
37
respuestas cortas como z = {**x, **y}realmente me estimulan
pcko1
1
Esto puede cambiarse cuando se acepta PEP-0584. Se implementará un nuevo operador sindical con la siguiente sintaxis:x | y
Callam Delaney
2
Cuando una respuesta necesita un resumen en la parte superior, es demasiado larga.
Gringo Suave
2
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 aproximándose a EOL en 2020-09.
Gringo Suave
1618

En su caso, lo que puede hacer es:

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

Esto, como lo desee, colocará 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(x.items() + y.items())
>>> z
{'a': 1, 'c': 11, 'b': 10}

Si usa Python 3, es solo un poco más complicado. Para crear z:

>>> z = dict(list(x.items()) + list(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)

Output: {'a': 1, 'c': 11, 'b': 10}
Thomas Vander Stichele
fuente
3
No use esto ya que es muy ineficiente. (Consulte los resultados de timeit a continuación). Puede haber sido necesario en los días Py2 si una función de contenedor no era una opción, pero esos días ya han pasado.
Gringo Suave
632

Una alternativa:

z = x.copy()
z.update(y)
Matthew Schinckel
fuente
83
Para aclarar por qué esto no cumple con los criterios proporcionados por la pregunta: no es una sola expresión y no devuelve z.
Alex
2
@neuronet por lo general, cada línea de tiempo solo mueve código que tiene que pasar a un componente diferente y lo resuelve allí. Este es definitivamente uno de los casos. pero otros lenguajes tienen mejores construcciones que python para esto. y tener una variante referencialmente transparente que devuelva su elemento es bueno tener algo.
Alex
12
Explíquelo de esta manera: si necesita poner dos líneas de comentarios que expliquen su única línea de código a las personas a quienes les entrega su código ... ¿realmente lo ha hecho en una sola línea? :) Estoy totalmente de acuerdo en que Python no es bueno para esto: debería haber una manera 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 "centrales" que las personas tienden a usar mucho.
eric
Bueno, si la gente insiste en convertirlo en una línea, siempre puedes hacer (lambda z: z.update(y) or z)(x.copy()): P
hasta
341

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 alguna clave que no sea de cadena, el hecho de que esto funcione es un abuso de los detalles 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 el código portátil compatible con versiones posteriores o de implementación cruzada, lo que realmente significa que debe evitarse por completo.

Carl Meyer
fuente
Funciona bien en Python 3 y PyPy y PyPy 3 , no puedo hablar con Jython o Iron. Dado que este patrón está documentado explícitamente (vea el tercer formulario de constructor en esta documentación), diría que no es un "detalle de implementación" sino un uso intencional de características.
amcgregor
55
@amcgregor Te perdiste la frase clave "si y tiene alguna clave que no sea 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 garantiza que todas sus claves sean cadenas, esta es una opción totalmente compatible.
Carl Meyer
214

Probablemente esta no sea una respuesta popular, pero es casi seguro que no desea hacerlo. Si desea una copia que sea una fusió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 () (pre 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 grandes listas que se desecharán tan pronto como se cree el dict combinado). update () puede funcionar de manera más eficiente, ya que puede ejecutarse a través del segundo dict ítem por ítem.

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 del diccionario solo se agregaron en Python 2.3, mientras que copy () y update () funcionarán en versiones anteriores.

Tony Meyer
fuente
150

En una respuesta de seguimiento, usted preguntó sobre el rendimiento 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 que ejecuta Python 2.5.2), la alternativa z2no solo es más corta y simple, sino también significativamente más rápida. Puedes verificar esto por ti mismo usando el timeitmódulo que viene con Python.

Ejemplo 1: diccionarios idénticos que asignan 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 más o menos. 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 predeterminado 3.)

Ejemplo 2: diccionarios no superpuestos que asignan 252 cadenas cortas a 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 libro!

Después de comparar esos dos, me preguntaba 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 llevó a concluir que z3es algo más rápido que z1, pero no tan rápido comoz2 . Definitivamente no vale la pena todo el tipeo adicional.

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 igualdad de condiciones con las expresiones, ninguna de las cuales modifica x o y, voy a hacer una copia de x en lugar de modificarla en su 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 para el código Python puro hacer algo mejor que esto. Y si puede hacerlo significativamente mejor en un módulo de extensión 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 utilizadict en muchos lugares; optimizar sus operaciones es un gran problema.

También podrías escribir esto como

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

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

zaphod
fuente
55
Esto no funciona en Python 3; items()no es catenable y iteritemsno existe.
Antti Haapala
127

En Python 3.0 ycollections.ChainMap versiones posteriores , puede usar qué grupos múltiples dictados u otras asignaciones juntas 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 extendido del diccionario PEP 448 . Esto es rápido y fácil:

>>> x = {'a':1, 'b': 2}
>>> y = y = {'b':10, 'c': 11}
>>> {**x, **y}
{'a': 1, 'b': 10, 'c': 11}
Raymond Hettinger
fuente
3
Pero uno debe ser cauteloso al usar ChainMap, hay una trampa de que si tiene claves duplicadas, los valores de la primera asignación se usan y cuando llama a un delsay, ChainMap c eliminará la primera asignación de esa clave.
Asesino
77
@Prerit ¿Qué más esperarías que hiciera? Esa es la forma normal en que funcionan los espacios de nombres encadenados. Considere cómo $ PATH funciona en bash. Eliminar un ejecutable en la ruta no excluye otro ejecutable con el mismo nombre más arriba.
Raymond Hettinger
2
@ Raymond Hettinger Estoy de acuerdo, acabo de agregar una advertencia. La mayoría de las personas pueden no saberlo. : D
Slayer
@Prerit Se podría emitir a dictevitar eso, es decir:dict(ChainMap({}, y, x))
wjandrea
113

Quería algo similar, pero con la capacidad de especificar cómo se fusionaron los valores en las claves duplicadas, así que lo pirateé (pero no lo probé en gran medida). Obviamente, esta no es una sola expresión, 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
rcreswick
fuente
88

Actualización recursiva / profunda de un dict

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 ediciones.

Stan
fuente
1
Esto no responde la pregunta. La pregunta claramente pide un nuevo diccionario, z, de los diccionarios originales, x e y, con valores de y que reemplazan a los de x, no un diccionario actualizado. Esta respuesta modifica y en el lugar al agregar valores de x. Peor aún, no copia estos valores, por lo que uno podría modificar aún 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 la copia profunda para copiar los valores.
Aaron Hall
1
@AaronHall estuvo de acuerdo en que esto no responde 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 aborda la anidación profunda. Aquí hay otra implementación de Martellibot: stackoverflow.com/questions/3232943/… .
Jérôme
72

La mejor versión que podría pensar mientras no usa 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 dict(x.items() + y.items())pero no tan rápido como n = copy(a); n.update(b), al menos en CPython. Esta versión también funciona en Python 3 si cambia iteritems()a items(), que se realiza automáticamente con la herramienta 2to3.

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

driax
fuente
71

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'}
Bilal Syed Hussain
fuente
¿De qué manera es mejor esta solución 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 .
Blackeagle52
14
A Guido no le gusta dict(x, **y)la razón (muy buena) de que se basa en ytener solo claves que son nombres válidos de argumentos de palabras clave (a menos que esté utilizando CPython 2.7, donde el constructor dict engaña). Esta objeción / restricción no se aplica a PEP 448, que generaliza la **sintaxis de desempaquetado para dictar literales. Entonces, esta solución tiene la misma concisión que dict(x, **y), sin la desventaja.
Carl Meyer
62
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.

Greg Hewgill
fuente
En python 3 obtendría TypeError: tipo (s) de operando no admitidos para +: 'dict_items' y 'dict_items' ... debe encapsular cada dict con list () como: dict (list (x.items ()) + list (y.items ()))
justSaid
49

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

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 malvado z2 mencionado anteriormente, pero fácil de entender y cambiar.

phobie
fuente
3
pero son tres declaraciones en lugar de una sola expresión
fortran
14
¡Si! Las soluciones de una sola expresión mencionadas son lentas o malvadas. Un buen código es legible y mantenible. Entonces el problema es la pregunta, no la respuesta. Deberíamos pedir la mejor solución de un problema, no una solución de una línea.
phobie
77
Pierda 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 mantenible).
Martineau
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 mejor mantenible. La línea adicional solo sería mala si costaría tiempo de ejecución.
phobie
47
def dict_merge(a, b):
  c = a.copy()
  c.update(b)
  return c

new = dict_merge(old, extras)

Entre estas respuestas dudosas y dudosas, este brillante ejemplo es la única y única buena manera de fusionar los 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 una función.

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

da:

{'color': 'red', 'owner': 'Carl', 'model': 'Ferrari'}
Sam Watkins
fuente
39

Si crees que las lambdas son malvadas, no sigas leyendo. Según lo solicitado, puede escribir la solución rápida y eficiente en la 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.

EMS
fuente
33

Sé pitónico. Usa una comprensión :

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

>>> print z
{'a': 1, 'c': 11, 'b': 10}
Robino
fuente
1
Como una función:def dictmerge(*args): return {i:d[i] for d in args for i in d}
jessexknight
1
Guarde una búsqueda iterando los pares clave / valor directamente:z={k: v for d in (x, y) for k, v in d.items()}
ShadowRanger
30

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

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

Para el 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())

De todos modos, prefiero esta notación, ya que parece más natural pensar en ella como una operación de unión de conjuntos que como 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 eny sean cadenas.

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

Las búsquedas buscan las asignaciones subyacentes sucesivamente hasta que se encuentra una clave.

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, 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.

beardc
fuente
22

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 unir un nombre y tuplas para anular el límite de una expresión de lambda. Siéntase libre de encogerse.

También podría 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}
Claudiu
fuente
22

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

import itertools as it
merge = lambda *args: dict(it.chain.from_iterable(it.imap(dict.iteritems, args)))

Y es 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}
reubano
fuente
16

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

Siguen 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 fuera una fusió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 profunda fusión que rcwesick dio 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 adelante una vez que escriba un método que haga una verdadera fusión.

Thanh Lim
fuente
11

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

Si no eres reacio a importar un módulo de biblioteca estándar, puedes hacer

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 Noneal éxito).

kjo
fuente
11

Si no te importa mutar x,

x.update(y) or x

Simple, legible, performante. Usted sabe update() siempre vuelve None, que es un valor falso. Entonces la expresión anterior siempre evaluará a x, después de actualizarla.

Los métodos de mutación en la biblioteca estándar (como .update()) regresan Nonepor convención, por lo que este patrón también funcionará en ellos. Si está utilizando un método que no sigue esta convención, es orposible que no funcione. Pero, en su lugar, puede usar una visualización e índice de tuplas para convertirlo en una sola expresión. Esto funciona independientemente de lo que evalúa el primer elemento.

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

Si aún no tiene xuna variable, puede usarla lambdapara hacer un local sin usar una declaración de asignación. Esto equivale a usar lambdacomo una expresión let , que es una técnica común en lenguajes funcionales, pero tal vez no es 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

Si desea una copia, el estilo PEP 448 es el más fácil {**x, **y}. Pero si eso no está disponible en su versión de Python (anterior), el patrón let también funciona aquí.

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

(Eso es, por supuesto, 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).

gilch
fuente
10

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

Podrías usar una lambda en su lugar.

Bijou Trouvaille
fuente
10

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, a mi modo de ver, 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}
Upandacross
fuente
1
Quizás te interese cytoolz.merge_with( toolz.readthedocs.io/en/latest/… )
bli
10

Es tan tonto que .updateno devuelve nada.
Solo uso una función auxiliar 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
Liberarse
fuente
10
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.

reetesh11
fuente
9

Esto se puede hacer con una sola comprensión dict:

>>> 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 breve.

RemcoGerlich
fuente
Sin embargo, sospecho que el rendimiento no será muy bueno; crear un conjunto de cada dict y luego solo iterar a través de las teclas significa otra búsqueda del valor cada vez (aunque relativamente rápido, todavía aumenta el orden de la función para escalar)
Breezer
2
Todo depende de la versión de Python que estemos usando. En 3.5 y superiores {** x, ** y} da el diccionario concatenado
Rashid Mv
9

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

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

a:

(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 es, a la myfunc((newdict := dict1.copy()).update(dict2))) , luego solo agregue or newdictal final (dado que updatedevuelve None, lo cual es falso, luego evaluará y regresará newdictcomo resultado de la expresión):

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

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

newdict = {**dict1, **dict2}

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

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

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

También es más extensible, ya que la fusión de tres dicts es obvia:

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

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

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

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

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

cualquiera de los cuales es, obviamente, mucho más feo, y además incluye las ineficiencias (ya sea un desperdicio temporal tuplede Nones para la separación de coma, o truthiness inútil prueba de cada update's Nonecambio deor separació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 tanto sets como dicts (ambos admiten copyyupdate , por lo tanto, el código funciona más o menos como lo esperaría)
  2. Espera recibir objetos arbitrarios como dict , no solo en dictsí mismo, y debe preservar el tipo y la semántica del lado izquierdo (en lugar de terminar con un plano dict). Si bien myspecialdict({**speciala, **specialb})podría funcionar, implicaría una temporalidad adicional dict, y si myspecialdicttiene características simples dict, no se puede conservar (por ejemplo, dictahora los s normales 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 en función de la últimoapariencia 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 expresión de asignación usa los métodos nombrados (que presumiblemente están sobrecargados para comportarse adecuadamente), nunca crea undicten absoluto (a menos que dict1ya fuera a dict), preservando el tipo original (y la semántica del tipo original), todo mientras se evitan los temporales.
ShadowRanger
fuente
8
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> x, z = dict(x), x.update(y) or x
>>> x
{'a': 1, 'b': 2}
>>> y
{'c': 11, 'b': 10}
>>> z
{'a': 1, 'c': 11, 'b': 10}
John La Rooy
fuente
Este método se sobrescribe xcon su copia. Si xes un argumento de función, esto no funcionará (ver ejemplo )
bartolo-otrit