Pregunta ¿Cómo combinar dos diccionarios en una sola expresión?


Tengo dos diccionarios de Python, y quiero escribir una sola expresión que devuelva estos dos diccionarios fusionados. los update() método sería lo que necesito, si devuelve su resultado en lugar de modificar un dict in-place.

>>> 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 dict fusionado final en zno x?

(Para ser extra claro, el último maneja el conflicto de forma dict.update() es lo que estoy buscando también.)


3211
2017-09-02 07:44


origen


Respuestas:


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

Para diccionarios x y y, z se convierte en un diccionario fusionado con valores de y reemplazando aquellos de x.

  • En Python 3.5 o superior,:

    z = {**x, **y}
    w = {'foo': 'bar', 'baz': 'qux', **y}  # merge a dict with literal values
    
  • 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

    z = merge_two_dicts(x, y)
    

Explicación

Supongamos que tiene dos dictados y desea fusionarlos en un nuevo dic sin alterar los dicts 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 dict sobreescribiendo 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 expresión única. Ahora se muestra como se implementó en el calendario de lanzamiento para 3.5, PEP 478, y ahora ha hecho su camino hacia Qué hay de nuevo en Python 3.5 documento.

Sin embargo, dado que muchas organizaciones todavía están en Python 2, es posible que desee hacer esto de una manera compatible hacia atrás. 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 xvalores de s, así 'b' señalará a 3 en nuestro resultado final.

Aún no está en Python 3.5, pero quiero un expresión única

Si aún no está en Python 3.5, o necesita escribir un código compatible con versiones anteriores, y quiere esto en una expresión única, el enfoque más eficaz mientras que 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 combinar un número indefinido de dictados, desde cero hasta 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 dicts. p.ej. dictados dados a a g:

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

y pares de valores clave en g tendrá prioridad sobre los dictados a a f, y así.

Críticas de otras respuestas

No use lo que ve en la respuesta aceptada anteriormente:

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

En Python 2, usted 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, y luego descarta las tres listas para crear el dict. En Python 3, esto fallará porque estás 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 crearlas explícitamente como listas, p. z = dict(list(x.items()) + list(y.items())). Esto es un desperdicio de recursos y poder de cálculo.

Del mismo modo, tomando la unión de items()en Python 3 (viewitems() en Python 2.7) también fallará cuando los valores sean objetos que no se puedan eliminar (como listas, por ejemplo). Incluso si tus valores son manejables, dado 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 son inigualables:

>>> x = {'a': []}
>>> y = {'b': []}
>>> dict(x.items() | y.items())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

Aquí hay un ejemplo donde y debería tener prioridad, pero en cambio el valor de x se conserva debido al orden arbitrario de conjuntos:

>>> x = {'a': 2}
>>> y = {'a': 1}
>>> dict(x.items() | y.items())
{'a': 2}

Otro truco que no deberías usar:

z = dict(x, **y)

Esto usa el dict constructor, y es muy rápido y eficiente desde el punto de vista de la memoria (incluso un poco más que nuestro proceso de dos pasos), pero a menos que sepa exactamente qué está sucediendo aquí (es decir, el segundo dict se pasa como argumentos de palabra clave al constructor dict). es difícil de leer, no es el uso previsto, por lo que no es Pythonic.

Aquí hay un ejemplo del uso remediado en django.

Los dicts están destinados a tomar claves manipulables (por ejemplo, conjuntos 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

Desde el lista de correo, Guido van Rossum, el creador del lenguaje, escribió:

Estoy bien con   declarar dict ({}, ** {1: 3}) ilegal, ya que después de todo es abuso de   el mecanismo.

y

Aparentemente dict (x, ** y) está funcionando como "cool hack" para "call   x.update (y) y devuelve x ". Personalmente, me parece más despreciable que   guay.

Entiendo (así como la comprensión de la creador del lenguaje) que el uso previsto para dict(**y) es para crear dictados con fines de legibilidad, p. ej .:

dict(a=1, b=10, c=11)

en lugar de

{'a': 1, 'b': 10, 'c': 11}

Respuesta a los 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 secuencia es una consecuencia directa de cómo funcionan los parámetros de palabra clave y no de una falta de comedia. Tampoco está usando el operador ** en este lugar, un abuso del mecanismo, de hecho ** fue diseñado precisamente para pasar dicts como palabras clave.

De nuevo, no funciona para 3 cuando las claves no son cadenas. El contrato de llamadas implícito es que los espacios de nombres toman dicts ordinarios, mientras que los usuarios solo deben pasar argumentos de palabras clave que son cadenas. Todos los demás callables lo impusieron. 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 incoherencia fue mala dado otras implementaciones de Python (Pypy, Jython, IronPython). Por lo tanto, se arregló 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.

Otro comentario:

dict(x.items() + y.items()) sigue siendo la solución más legible para Python 2. La legibilidad cuenta.

Mi respuesta: merge_two_dicts(x, y) de hecho, 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.

Ad-hocs menos eficaces pero correctos

Estos enfoques son menos efectivos, pero proporcionarán un comportamiento correcto. Ellos estarán mucho menos mejor que copy y update o el nuevo desembalaje porque iteran a través de cada par clave-valor en un nivel de abstracción más alto, pero hacer respetar el orden de precedencia (estos últimos tienen precedencia)

También puede encadenar los dicts 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 quizás tan pronto como 2.4 cuando se introdujeron expresiones de generador):

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 (PAP de Deadsnakes):

>>> 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 en diccionarios


3336
2017-11-10 22:11



En tu caso, lo que puedes hacer es:

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

Esto, como lo desee, ponga la dict final en zy crea el valor de la clave b ser correctamente anulado por el segundo (y) valor de 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 usas Python 3, es solo un poco más complicado. Crear z:

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

1438
2017-09-02 07:50



Una alternativa:

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

546
2017-09-02 13:00



Otra opción más concisa:

z = dict(x, **y)

Nota: esto se ha convertido en una respuesta popular, pero es importante señalar que si y tiene 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, ni en PyPy, IronPython o Jython. También, Guido no es un fan. Por lo tanto, no puedo recomendar esta técnica para el código portátil compatible o compatible con versiones posteriores, lo que realmente significa que debe evitarse por completo.


272
2017-09-02 15:52



Probablemente esta no sea una respuesta popular, pero es casi seguro que no quieres hacer esto. Si quiere una copia fusionada, utilice copy (o deepcopy, dependiendo de lo que quieras) y luego actualiza. 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, eso representa una gran cantidad de gastos generales (dos grandes listas que se descartarán tan pronto como se cree el dict fusionado). update () puede funcionar de manera más eficiente, ya que puede ejecutarse a través del segundo dict elemento por elemento.

En términos de hora:

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

OMI, la pequeña ralentizació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.


167
2017-09-08 11:16



En una respuesta de seguimiento, 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 ordinario ejecutando Python 2.5.2), alternativa z2 no solo es más corto y simple, sino también significativamente más rápido. Puede verificar esto usted mismo usando el timeit módulo que viene con Python.

Ejemplo 1: diccionarios idénticos que mapean 20 enteros consecutivos consigo 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

z2 gana por un factor de 3.5 o menos. Distintos diccionarios parecen arrojar resultados bastante diferentes, pero z2 siempre parece salir adelante. (Si obtiene resultados inconsistentes para el mismo prueba, intenta pasar -r con un número más grande que el predeterminado 3.)

Ejemplo 2: diccionarios no superpuestos que mapean 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. ¡Es una gran victoria en mi libro!

Después de comparar esos dos, me preguntaba si z1El bajo rendimiento de la empresa se puede atribuir a la sobrecarga de la construcción de 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.

% 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 llevan a concluir que z3 es algo más rápido que z1, pero no tan rápido como z2. Definitivamente no vale la pena todo el tipeo extra.

A esta discusión todavía le falta algo importante, que es una comparación del rendimiento de estas alternativas con la forma "obvia" de fusionar dos listas: usar el update mé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, z0 y z2 parece 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 hacer mucho mejor en un módulo de extensión C, me 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 usa dict en muchos lugares; optimizar sus operaciones es un gran problema.

También puedes escribir esto como

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

como Tony lo hace, pero (como es lógico) la diferencia en la notación resulta no tener ningún efecto mensurable en el rendimiento. Use el que le parezca correcto. 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.


116
2017-10-23 02:38



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

86
2017-09-04 19:08