¿Fusionar diccionarios en Python? La respuesta obvia no es la mejor

A menudo, la forma más intuitiva de combinar diccionarios en Python esconde trampas de rendimiento y legibilidad. Te muestro las opciones y por qué algunas son mejores.

En mi experiencia como desarrollador, me he topado muchas veces con el problema de fusionar dos diccionarios en Python, donde los valores del segundo diccionario deben sobrescribir los del primero si hay claves repetidas. Es una operación común, y la verdad, hay varias formas de hacerlo. Pero ojo que no todas son igual de buenas, y a veces la solución más directa o que parece más “elegante” a primera vista, te puede jugar una mala pasada.

De hecho, si buscas una solución de una sola línea, lo más probable es que te encuentres con algo como dict(list(x.items()) + list(y.items())). Esta línea funciona, sí. Pero no me convence para nada. Internamente, esto crea dos listas temporales con los ítems de tus diccionarios, luego las concatena en una tercera lista, y finalmente construye un nuevo diccionario a partir de esa lista gigante. ¿Te imaginas el overhead si tus diccionarios son grandes? Es un desperdicio de memoria y ciclos de CPU.

Así que, si bien es una “expresión única”, a mí me frustra un poco la sobreingeniería cuando hay caminos más claros, eficientes y, francamente, más pitónicos. Vamos a revisar las alternativas.

La forma clásica y robusta: copy() y update()

Antes de que Python introdujera sintaxis más concisas para esto, la manera recomendada y más clara de fusionar diccionarios era utilizando los métodos copy() y update(). Y sabes, es una solución que sigue siendo perfectamente válida y muy legible hoy.

La lógica es simple: creas una copia del primer diccionario y luego le actualizas los valores con el segundo. Así, los valores del segundo diccionario sobrescribirán cualquier clave existente del primero, y las claves nuevas del segundo se añadirán.

x = {'a': 1, 'b': 2, 'c': 3}
y = {'b': 10, 'd': 40}

def merge_two_dicts(x, y):
    z = x.copy()  # Empezar con una copia de x
    z.update(y)   # Actualizar con los valores de y (y sobrescribir si hay duplicados)
    return z

z = merge_two_dicts(x, y)
print(z) # Resultado: {'a': 1, 'b': 10, 'c': 3, 'd': 40}

Esta es mi opción por defecto cuando la simplicidad y la compatibilidad con versiones antiguas de Python (incluso 2.7, si es que todavía te toca lidiar con eso) son clave. Es explícita, fácil de entender y eficiente porque no crea listas intermedias innecesarias.

Las soluciones modernas (Python 3.5+): Desempaquetado y el operador |

Python ha evolucionado, y con las versiones más recientes, tenemos formas mucho más concisas y elegantes de hacer esta fusión. Esto es lo que yo prefiero usar si el proyecto me lo permite.

Desempaquetado con ** (Python 3.5+)

A partir de Python 3.5 (PEP 448), puedes usar el operador de desempaquetado ** dentro de un diccionario literal. Cuando lo usas con múltiples diccionarios, los que se desempaquetan al final tienen precedencia.

x = {'a': 1, 'b': 2, 'c': 3}
y = {'b': 10, 'd': 40}

z = {**x, **y}
print(z) # Resultado: {'a': 1, 'b': 10, 'c': 3, 'd': 40}

Esta es una solución que me encanta. Es concisa, clara y una “expresión única” de verdad, sin los trucos de crear listas intermedias. Me acuerdo que esto me costó un PR una vez, porque en mi entusiasmo lo usé en un proyecto que aún corría en Python 3.4. El revisor me lo echó para atrás con justa razón. Es un buen recordatorio de que siempre hay que tener la versión de Python en mente.

El operador de unión | (Python 3.9+)

La solución más reciente y, en mi opinión, la más elegante y directa, llegó con Python 3.9 (PEP 584). Ahora puedes usar el operador de unión | para fusionar diccionarios, y el operador de actualización |= para hacerlo in-place.

x = {'a': 1, 'b': 2, 'c': 3}
y = {'b': 10, 'd': 40}

z = x | y
print(z) # Resultado: {'a': 1, 'b': 10, 'c': 3, 'd': 40}

# También puedes actualizar in-place:
x |= y
print(x) # Resultado: {'a': 1, 'b': 10, 'c': 3, 'd': 40}

Este es el futuro, y si tu proyecto está en Python 3.9 o superior, no hay mejor forma. Es intuitivo, legible y eficiente. Siempre que puedo, apunto a usar esta sintaxis.

¿En qué se equivoca la gente con esto?

Mira, al final del día, la gente se equivoca en dos cosas principales:

  1. Priorizar la “expresión única” sobre la eficiencia o la legibilidad: La solución que mencioné al principio (dict(list(x.items()) + list(y.items()))) es un claro ejemplo. Sí, es una línea de código, pero a un costo que no vale la pena. No pienses que por ser una línea es mejor.
  2. Ignorar la compatibilidad de versiones: Como me pasó a mí con el PR, usar características de versiones de Python más nuevas en ambientes que no las soportan es un error común. Siempre verifica la versión de Python de tu entorno antes de adoptar las últimas sintaxis.

Mi consejo es simple: si estás en Python 3.9+, usa |. Si estás entre Python 3.5 y 3.8, {**x, **y} es tu mejor amigo. Y si por alguna razón sigues en una versión anterior, o simplemente quieres una claridad a prueba de balas, la combinación de copy() y update() nunca te va a fallar.

Jorge RequenaDeveloper full-stack · Chile