Copy y deepcopy en Python: por qué tu "copia" no es una copia
Esta lección es una traducción del original en inglés.Read in EnglishAvisar de un error de traducción
Imagina que tienes una carpeta llena de documentos. Fotocopias la carpeta y se la das a un amigo. Pero hay una trampa: las fotos que hay dentro de la carpeta no son copias. Son las mismas fotos de papel. Si tu amigo le dibuja un bigote a una, tu foto original también queda arruinada.
Así es exactamente como funciona la copia por defecto en Python. Crees que has hecho una copia independiente, pero los objetos internos siguen compartidos. Es uno de los errores más confusos de Python, y hace tropezar por igual a principiantes y a programadores con experiencia.
En este tutorial aprenderás la diferencia entre asignación, copia superficial (shallow copy) y copia profunda (deep copy). Al terminar, sabrás exactamente cuándo usar cada una y no volverás a caer en un error de "referencia compartida".
¿Por qué cambiar una "copia" cambia el original?
Empecemos por la sorpresa más común. Cuando escribes b = a, NO estás haciendo una copia. Estás creando un segundo nombre para el mismo objeto. Tanto a como b apuntan a los mismos datos en memoria.
[1, 2, 3, 4] [1, 2, 3, 4]
Esto pasa porque a y b son dos etiquetas pegadas en la misma caja. Cuando metes algo nuevo en la caja usando la etiqueta b, también lo ves cuando miras por la etiqueta a.
Puedes comprobar que dos variables apuntan al mismo objeto con el operador is o revisando su id().
True 80036304 80036304
¿Qué es una copia superficial?
Una copia superficial crea un contenedor exterior nuevo, pero NO copia los objetos que hay dentro. La lista nueva es independiente en el nivel superior, pero cualquier objeto anidado sigue compartido con el original.
Hay varias formas de hacer una copia superficial de una lista.
[1, 2, 3] [1, 2, 3, 99]
Para los diccionarios, usa .copy() o dict().
{'name': 'Alice', 'age': 25}
{'name': 'Alice', 'age': 30}Esto parece perfecto. Pero aquí está la trampa: la copia superficial solo copia un nivel. Si tu lista contiene otras listas (o diccionarios), esos objetos internos siguen compartidos.
3 4 [1, 2, 99] [1, 2, 99]
original = [[1, 2], [3, 4]]
shallow = original.copy()
shallow[0].append(99)
print(original) # [[1, 2, 99], [3, 4]]import copy
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
deep[0].append(99)
print(original) # [[1, 2], [3, 4]]¿Qué es una copia profunda y cuándo la necesitas?
Una copia profunda crea un clon completamente independiente. Copia el objeto exterior Y copia de forma recursiva cada objeto que hay dentro, sin importar cuántos niveles de anidación tenga. El original y la copia no comparten nada.
Para hacer una copia profunda, necesitas el módulo copy de la biblioteca estándar de Python.
[[1, 2], [3, 4], [5, 6]] [[1, 2, 99], [3, 4], [5, 6], [7, 8]]
La copia profunda también funciona con diccionarios, incluidas las estructuras con muchos niveles de anidación.
Alice [90, 85, 92] ['star', 'gold'] Bob [90, 85, 92, 100] ['star', 'gold', 'diamond']
Por dentro: cómo funcionan las referencias en Python
Para entender de verdad las copias, necesitas saber cómo guarda Python los datos. Cada dato vive en algún lugar de la memoria como un objeto. Las variables son solo nombres que hacen referencia a esos objetos (apuntan a ellos).
La asignación copia la referencia. La copia superficial crea un contenedor nuevo con las mismas referencias dentro. La copia profunda crea un contenedor nuevo con referencias nuevas que apuntan a copias nuevas de todo lo que hay dentro.
True True False
Aquí tienes un resumen de los tres enfoques en forma de tabla.
Assignment: same outer, same inner Shallow copy: new outer, same inner Deep copy: new outer, new inner
¿Cuándo usar cada tipo de copia?
Usa la asignación (b = a) cuando quieras a propósito dos nombres para los mismos datos. Es habitual al pasar datos a funciones: normalmente quieres que la función trabaje con el original.
Usa una copia superficial cuando tengas una lista o un diccionario plano (sin objetos mutables anidados) y necesites una copia independiente. Esto cubre la mayoría de los casos del día a día.
Usa una copia profunda cuando tengas objetos mutables anidados (listas de listas, diccionarios con listas como valores, etc.) y necesites un clon totalmente independiente. Es imprescindible cuando quieres modificar la copia sin ningún riesgo de afectar al original.
Ejercicios de práctica
Lee el código con atención. ¿Qué va a imprimir? Recuerda que = con listas crea un alias, no una copia.
Escribe la salida exacta.
El código de abajo intenta crear una copia de seguridad de la lista original antes de modificarla. ¡Pero la copia de seguridad también cambia! Corrige el código para que la copia de seguridad no cambie.
Salida esperada:
Original: [1, 2, 3, 4]
Backup: [1, 2, 3]Este código usa una copia superficial con datos anidados. Léelo con atención y predice qué se va a imprimir.
Escribe la salida exacta.
Crea una copia totalmente independiente de los datos de original usando una copia profunda. Luego añade la puntuación 100 a la lista de puntuaciones de la copia. Imprime las puntuaciones del original y las de la copia en líneas separadas para demostrar que son independientes.
Este juego guarda un punto de control con el estado del jugador y luego el jugador recoge un objeto. ¡Pero cuando cargamos el punto de control, el objeto ya está ahí! Corrige el código para que el punto de control sea una instantánea realmente independiente.
Salida esperada:
Checkpoint inventory: ['sword', 'shield']
Current inventory: ['sword', 'shield', 'potion']