Python: objeto de copia profunda y superficial

En este artículo, aprendemos la diferencia entre la copia profunda y la copia superficial de objetos en Python, y cómo hacer ambas cosas con la biblioteca de copia.

Introducción

En este artículo, veremos cómo hacer una copia profunda y superficial de los objetos en Python.

La respuesta corta es que puedes usar métodos del módulo copiar, para ambas operaciones:

1
2
3
4
import copy

shallow_copy_list = copy.copy(original_list)
deepcopy_list = copy.deepcopy(original_list)

Aunque, ¿qué significa copiar algo de manera superficial o profunda?

En las secciones siguientes, profundizaremos en lo que significan estos términos, cómo trata Python las referencias a objetos y los objetos en la memoria, y por qué estos dos métodos funcionan de la forma en que lo hacen.

Copia superficial de un objeto en Python

Cuando usamos declaraciones de asignación (=) en Python para crear copias de objetos compuestos, como listas o instancias de clase o básicamente cualquier objeto que contenga otros objetos, Python no clona el objeto en sí. En su lugar, simplemente vincula la referencia al objeto de destino.

Imagina que tenemos una lista con los siguientes elementos:

1
original_list =[[1,2,3], [4,5,6], ["X", "Y", "Z"]]

Si tratamos de copiar nuestra lista original usando la declaración de asignación de la siguiente manera:

1
2
shallow_copy_list = original_list
print(shallow_copy_list)

Puede parecer que clonamos nuestro objeto y ahora tenemos dos de ellos:

1
[[1,2,3], [4,5,6], ['X', 'Y', 'Z']]

Pero, ¿realmente tenemos dos objetos? No, no lo hacemos. Tenemos dos variables de referencia que apuntan al mismo objeto en la memoria. Esto se puede verificar fácilmente imprimiendo la ID del objeto en la memoria para ambos:

1
2
id(original_list) # 4517445712
id(shallow_copy_list) # 4517445712

Se puede observar una prueba más tangible de esto al intentar cambiar un valor en cualquiera de "las dos listas", mientras que en realidad cambiamos la misma lista y ambos punteros apuntan al mismo objeto en la memoria.

Accedamos al último elemento del objeto apuntado por original_list:

1
2
3
# Last element of last element
original_list[-1][-1] = "ZZZ"
print(original_list)

Esto resulta en:

1
[[1, 2, 3], [4, 5, 6], ['X', 'Y', 'ZZZ']]

Sabiendo que ambas variables de referencia apuntan al mismo objeto, imprimir shallow_copy_list devolverá el mismo resultado:

1
print(shallow_copy_list)
1
[[1, 2, 3], [4, 5, 6], ['X', 'Y', 'ZZZ']]

Copia superficial es el proceso de copiar una referencia a un objeto y almacenarla en una nueva variable. La original_list y shallow_copy_list son meras referencias que apuntan a las mismas direcciones en la memoria (RAM), que almacenan los valores de [[1, 2, 3], [4, 5, 6], ['X' , 'Y', 'ZZZ']].

Una ilustración de la copia superficial

También es posible crear una copia superficial de un objeto usando un segmento de la lista completa y la instrucción de asignación:

1
slice_shallow_copy_list = original_list[:]

Otra forma de copiar superficialmente es usar el módulo “copiar” de la biblioteca estándar de Python.

Para usar el módulo copy, primero debemos importarlo:

1
import copy

Ahora podemos usar el método copy() del módulo copy:

1
second_shallow_copy_list = copy.copy(original_list)

Imprímalos ambos para ver si hacen referencia a los mismos valores:

1
2
print(original_list)
print(second_shallow_copy_list)

Como era de esperar, lo hacen:

1
2
[[1, 2, 3], [4, 5, 6], ['X', 'Y', 'ZZZ']]
[[1, 2, 3], [4, 5, 6], ['X', 'Y', 'ZZZ']]

Por lo general, le gustaría copiar un objeto compuesto, por ejemplo, al comienzo de un método, luego modificar el clon, pero mantener el objeto original como estaba para poder usarlo nuevamente en algún momento.

Para lograr esto, necesitamos copia profunda del objeto. Ahora aprendamos qué es una copia profunda y cómo hacer una copia profunda de un objeto compuesto.

Copia profunda de un objeto en Python

La copia profunda de un objeto significa realmente clonar el objeto y sus valores en una nueva copia (instancia) en la memoria, con esos mismos valores.

En lugar de crear una nueva referencia a los mismos valores, con la copia profunda, podemos crear un nuevo objeto que sea independiente de los datos originales pero que contenga los mismos valores.

En un proceso típico de copia profunda, primero, se crea una nueva referencia de objeto, luego todos los objetos secundarios se agregan al objeto principal de forma recursiva.

De esta manera, a diferencia de una copia superficial, cualquier modificación del objeto original no se refleja en el objeto de copia (o viceversa).

Aquí hay una ilustración simple de una copia profunda típica:

Una ilustración de la copia profunda

Para realizar una copia profunda de un objeto en Python, usamos el método deepcopy() del módulo copy.

Importemos el módulo de copia y creemos una copia profunda de una lista:

1
2
3
4
import copy
 
original_list = [[1,2,3], [4,5,6], ["X", "Y", "Z"]]
deepcopy_list = copy.deepcopy(original_list)

Ahora imprimamos nuestras listas para asegurarnos de que los resultados sean los mismos, así como sus ID como prueba de su singularidad:

1
2
print(id(original_list), original_list)
print(id(deepcopy_list), deepcopy_list)

El resultado confirma que nos hemos creado una copia genuina:

1
2
4517599280, [[1, 2, 3], [4, 5, 6], ['X', 'Y', 'Z']]
4517599424, [[1, 2, 3], [4, 5, 6], ['X', 'Y', 'Z']]

Ahora intentemos modificar nuestra lista original cambiando el último elemento de la última lista a "O", y luego imprímalo para ver el resultado:

1
2
original_list[-1][-1] = "O"
print(original_list)

Obtenemos los resultados esperados:

1
[[1, 2, 3], [4, 5, 6], ['X', 'Y', 'O']]

Ahora, si seguimos adelante e intentamos imprimir nuestra lista de copias:

1
print(deepcopy_list)

La modificación anterior no se reflejaba en esta lista:

1
[[1, 2, 3], [4, 5, 6], ['X', 'Y', 'Z']]

Recuerda que los métodos copy() y deepcopy() son aplicables a otros objetos compuestos. Esto significa que también puede usarlos para crear copias de instancias de clase.

Conclusión

En este artículo, aprendimos lo que significa la copia superficial y la copia profunda de un objeto.

También aprendimos que podemos usar el método copy() del módulo copy para crear una copia superficial, y el método deepcopy() para crear una copia profunda de los objetos compuestos.

Licensed under CC BY-NC-SA 4.0