Tengo dos diccionarios Python, y quiero escribir una sola expresión que devuelva estos dos diccionarios, combinados. El método update() 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 en z, no en x?

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

4573
Carl Meyer 2 sep. 2008 a las 11:44

30 respuestas

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.

37
EMS 23 nov. 2011 a las 18:20

Sé que esto realmente no se ajusta a los detalles específicos de las preguntas ("one liner"), pero dado que ninguna de las respuestas anteriores fue en esta dirección, mientras que muchas respuestas abordaron el problema de rendimiento, sentí Debería aportar mis pensamientos.

Dependiendo del caso de uso, puede que no sea necesario crear un diccionario combinado "real" de los diccionarios de entrada dados. Una vista que hace esto puede ser suficiente en muchos casos, i. mi. un objeto que actúa como lo haría el diccionario combinado sin calcularlo por completo. Una versión perezosa del diccionario combinado, por así decirlo.

En Python, esto es bastante simple y se puede hacer con el código que se muestra al final de mi publicación. Esto dado, la respuesta a la pregunta original sería:

z = MergeDict(x, y)

Cuando se utiliza este nuevo objeto, se comportará como un diccionario combinado, pero tendrá un tiempo de creación constante y una huella de memoria constante, sin tocar los diccionarios originales. Crearlo es mucho más barato que en las otras soluciones propuestas.

Por supuesto, si usa mucho el resultado, en algún momento alcanzará el límite en el que crear un diccionario real fusionado hubiera sido la solución más rápida. Como dije, depende de su caso de uso.

Si alguna vez sintió que preferiría tener una dict real fusionada, llamar a dict(z) la produciría (pero, por supuesto, es mucho más costosa que las otras soluciones, por lo que vale la pena mencionarla).

También puede usar esta clase para hacer una especie de diccionario de copia en escritura:

a = { 'x': 3, 'y': 4 }
b = MergeDict(a)  # we merge just one dict
b['x'] = 5
print b  # will print {'x': 5, 'y': 4}
print a  # will print {'y': 4, 'x': 3}

Aquí está el código directo de MergeDict:

class MergeDict(object):
  def __init__(self, *originals):
    self.originals = ({},) + originals[::-1]  # reversed

  def __getitem__(self, key):
    for original in self.originals:
      try:
        return original[key]
      except KeyError:
        pass
    raise KeyError(key)

  def __setitem__(self, key, value):
    self.originals[0][key] = value

  def __iter__(self):
    return iter(self.keys())

  def __repr__(self):
    return '%s(%s)' % (
      self.__class__.__name__,
      ', '.join(repr(original)
          for original in reversed(self.originals)))

  def __str__(self):
    return '{%s}' % ', '.join(
        '%r: %r' % i for i in self.iteritems())

  def iteritems(self):
    found = set()
    for original in self.originals:
      for k, v in original.iteritems():
        if k not in found:
          yield k, v
          found.add(k)

  def items(self):
    return list(self.iteritems())

  def keys(self):
    return list(k for k, _ in self.iteritems())

  def values(self):
    return list(v for _, v in self.iteritems())
6
Alfe 18 may. 2016 a las 15:57
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.

58
Greg Hewgill 2 sep. 2008 a las 07:49

Usando una comprensión dict, puede

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

dc = {xi:(x[xi] if xi not in list(y.keys()) 
           else y[xi]) for xi in list(x.keys())+(list(y.keys()))}

Da

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

Tenga en cuenta la sintaxis para if else en comprensión

{ (some_key if condition else default_key):(something_if_true if condition 
          else something_if_false) for key, value in dict_.items() }
5
kiriloff 27 may. 2013 a las 09:04

En python3, el método items 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 método viewitems 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 truco dict(x, **y) no funcionará en python 3 a menos que las claves en y sean cadenas.

Además, Raymond Hettinger's Chainmap answer es bastante elegante, ya que puede tomar un número arbitrario de dictados como argumentos, pero de los documentos parece que mira secuencialmente a través de 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.

29
Community 23 may. 2017 a las 12:34

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.

10
Bijou Trouvaille 19 jul. 2013 a las 05:49

Es tan tonto que .update no 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
7
GetFree 2 mar. 2014 a las 01:44

Si no te importa mutar x,

x.update(y) or x

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

Los métodos de mutación en la biblioteca estándar (como .update()) devuelven None por convención, por lo que este patrón también funcionará en esos. Si está utilizando un método que no sigue esta convención, entonces or puede no funcionar. 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 x en una variable, puede usar lambda para hacer un local sin usar una instrucción de asignación. Esto equivale a utilizar lambda como let expression , que es una técnica común en lenguajes funcionales, pero que puede no ser 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).

8
gilch 17 ene. 2020 a las 06:00
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'}
46
Sam Watkins 6 ago. 2012 a las 09:30

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.

84
Dawid Gosławski 18 dic. 2015 a las 11:19

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.

16
Thanh Lim 3 ago. 2012 a las 23:36
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.

10
reetesh11 30 nov. 2015 a las 13:04

En su caso, lo que puede hacer es:

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

Esto, como lo desee, pondrá el dict final en z, y hará que el valor de la clave b sea 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}
1587
wim 29 ago. 2018 a las 17:18

Abuso que conduce a una solución de una 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é de lambda para 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}
22
Community 23 may. 2017 a las 12:34

(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 bit or a en lambda es necesario porque dict.update siempre devuelve None en caso de éxito).

11
kjo 28 mar. 2016 a las 13:13

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 es fácil de entender y cambiar.

47
phobie 14 oct. 2011 a las 16:12

Una alternativa:

z = x.copy()
z.update(y)
620
Matthew Schinckel 2 sep. 2008 a las 13:00

En Python 3.0 y versiones posteriores , puede usar { {X0}} que agrupa 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 = 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 PEP 448 diccionario ampliado de empaquetado y desempaquetado. 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}
112
Neuron 20 nov. 2019 a las 17:03

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

sum tiene un mal rendimiento. Ver https://mathieularose.com/how -not-to-flatten-a-list-of-lists-in-python /

21
Mathieu Larose 2 oct. 2016 a las 18:16

Solución simple usando itertools 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}
21
reubano 6 sep. 2016 a las 11:30

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, dependiendo en lo que quieres) 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, 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 time:

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

207
twasbrillig 5 ago. 2014 a las 23:56

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}
10
upandacross 3 dic. 2013 a las 18:11

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}
64
Bilal Syed Hussain 26 feb. 2015 a las 21:27

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
110
Rainy 13 sep. 2014 a las 19:56

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.

9
RemcoGerlich 17 jul. 2015 a las 14:47

La mejor versión que podría pensar mientras no uso 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 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.

69
driax 14 oct. 2010 a las 18:55

Sé 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}
32
Robino 29 sep. 2016 a las 10:45
>>> 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}
8
John La Rooy 13 nov. 2013 a las 10:01

Otra opción más concisa:

z = dict(x, **y)

Nota : esta se ha convertido en una respuesta popular, pero es importante señalar que si y tiene teclas que no son de cadena, el hecho de que esto funcione es un abuso de un CPython detalle de implementación, y no funciona en Python 3, ni 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.

331
Carl Meyer 21 ene. 2016 a las 06:43

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 z2 no solo es más corta y simple, sino también significativamente más rápida. Puede verificar esto usted mismo utilizando el módulo timeit 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

z2 gana por un factor de 3.5 más o menos. Parece que diferentes diccionarios producen resultados bastante diferentes, pero z2 siempre parece salir adelante. (Si obtiene resultados inconsistentes para la prueba same , intente pasar -r con 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 el bajo rendimiento de z1 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.

% 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

Llévame a la conclusión de 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 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: usando el método update. 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 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, z0 y z2 parecen 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 usa dict 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) la diferencia en la notación resulta no tener 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.

143
the Tin Man 10 ene. 2015 a las 02:32
38987