La asignación de una variable (por ejemplo, a = b) en Python no copia el objeto en sí, sino que simplemente crea un nuevo nombre (referencia) para el objeto existente. Los cambios a través de un nombre serán visibles a través del otro si se trata de un objeto mutable.
Copia superficial (shallow copy), por ejemplo a través de copy.copy(obj) o un corte [:] para una lista, crea un nuevo objeto de nivel superior, pero los objetos anidados dentro se copian por referencia (es decir, ambas estructuras "apuntan" a los mismos subcontenedores). Si se modifican los objetos anidados, los cambios serán visibles a través de ambos objetos.
Ejemplo:
import copy lst1 = [[1,2], [3,4]] lst2 = copy.copy(lst1) # o lst1[:] lst1[0][0] = 100 print(lst2) # [[100, 2], [3, 4]]
lst2 es una nueva lista, pero su primer elemento es la misma lista anidada.
Pregunta: ¿Cuál es la diferencia entre lst2 = lst1[:] y lst2 = copy.copy(lst1)?
Respuesta: En la práctica, para listas normales (de un solo nivel), no hay diferencia: ambos métodos realizan una copia superficial de la lista. Sin embargo, para clases de contenedores personalizados, puede haber un comportamiento diferente (por ejemplo, si se implementa su propio método __copy__). De la misma manera, para otros tipos (dict, set, etc.) es más seguro utilizar el módulo especializado copy.
import copy lst1 = [1, 2, 3] lst2 = lst1[:] lst3 = copy.copy(lst1) print(lst2 == lst3) # True
Historia
En un proyecto de manejo de configuraciones, un desarrollador duplicó los parámetros predeterminados a través de la asignación params = default_params y pensó que podía cambiarlos "aisladamente". Al final, cualquier cambio en cualquier copia llevaba a un efecto cascada en todas las partes de la aplicación, ya que en realidad se trabajaba con un solo objeto.
Historia
Un programador inexperto usó una copia superficial de la lista para almacenar el estado del juego (game_states = states[:]). Con una estructura anidada (listas de figuras en el campo), los cambios dentro de un estado "filtraban" en otros, rompiendo la historia de retrocesos y repeticiones de movimientos.
Historia
Al intentar clonar datos en una aplicación orientada a objetos, la elección estaba entre un corte y copy.copy(). Pero en la estructura se encontró con su propia clase con su propio método de copia, que solo fue considerado al usar copy.copy(). El corte ignoró la lógica de copia del objeto, y surgieron errores no evidentes.