1. Introducción

El uso de alias puede hacer que un programa sea difícil de leer, porque los cambios hechos en un lugar pueden tener efectos inesperados en otro lugar. Es difícil estar al tanto de todas las variables a las que puede apuntar un objeto dado.

# Crear una lista
lista_original = [1, 2, 3]

# Crear un alias de la lista
alias_lista = lista_original

# Modificar el alias
alias_lista.append(4)

print(lista_original)  # Salida: [1, 2, 3, 4]
print(alias_lista)     # Salida: [1, 2, 3, 4]

En Python, el copiado de objetos puede hacerse de dos maneras: superficial (shallow copy) y profundo (deep copy). Ambas técnicas se utilizan para duplicar objetos, pero se diferencian en la forma en que manejan los objetos anidados, como listas dentro de listas o diccionarios que contienen otros diccionarios.

2. Copia superficial (shallow copy)

Copiar un objeto es, muchas veces, una alternativa a la creación de un alias. El módulo copy contiene una función llamada copy que puede duplicar cualquier objeto:

import copy
p1 = Punto()
p1.x = 3
p1.y = 4
p2 = copy.copy(p1)
p1 == p2 # 0
mismoPunto(p1, p2) # 1

Una vez que hemos importado el módulo copy, podemos usar el método copy para hacer un nuevo Punto, p1 y p2 no son el mismo punto, pero contienen los mismos datos.

Para copiar un objeto simple como un Punto, que no contiene objetos incrustados, copy es suficiente. Esto se llama copiado superficial. La copia superficial crea un nuevo objeto, pero no copia los objetos contenidos dentro del original, sino que mantiene referencias a estos. Esto significa que los cambios en los subobjetos (elementos anidados) dentro del original se reflejarán también en la copia.

  • Se puede realizar con el método .copy() (para listas y diccionarios) o con la función copy() del módulo copy.
  • Es útil cuando solo queremos duplicar el contenedor, pero no los elementos anidados.

Para algo como un Rectangulo, que contiene una referencia a un Punto, copy no lo hace del todo bien. Copia la referencia al objeto Punto, de modo que tanto el Rectangulo viejo como el nuevo apuntan a un único Punto.

Si creamos una caja, b1, de la forma habitual y entonces hacemos una copia, b2, usando copy, el diagrama de estados resultante se ve así:

Es casi seguro que esto no es lo que queremos. En este caso, la invocación de agrandaRect sobre uno de los Rectangulos no afectaría al otro, ¡pero la invocación de mueveRect sobre cualquiera afectaria a ambos! Este comportamiento es confuso y propicia los errores.

Otro ejemplo:

import copy

# Objeto original con una lista anidada
original = [[1, 2, 3], [4, 5, 6]]

# Copia superficial
shallow_copy = copy.copy(original)

# Modificación del subobjeto en el original
original[0][0] = 'X'

print("Original:", original)  # [['X', 2, 3], [4, 5, 6]]
print("Copia superficial:", shallow_copy)  # [['X', 2, 3], [4, 5, 6]]

Como se ve en el ejemplo, al cambiar el primer elemento del subobjeto de la lista original, el cambio también se refleja en la copia superficial, ya que ambas listas anidadas apuntan al mismo objeto en memoria.

3. Copia profunda (deep copy)

Afortunadamente, el módulo copy contiene un método llamado deepcopy que copia no sólo el objeto sino también cualesquiera objetos incrustados. No le sorprenderá saber que esta operación se llama copia profunda (deep copy).

b2 = copy.deepcopy(b1)

Ahora b1 y b2 son objetos totalmente independientes.

Podemos usar deepcopy para reescribir agrandaRect de modo que en lugar de modificar un Rectangulo existente, cree un nuevo Rectangulo que tiene la misma localización que el viejo pero nuevas dimensiones:

def agrandaRect(caja, danchura, daltura) :
	import copy
	nuevaCaja = copy.deepcopy(caja)
	nuevaCaja.anchura = nuevaCaja.anchura + danchura
	nuevaCaja.altura = nuevaCaja.altura + daltura
	return nuevaCaja

Ejercicio Como ejercicio, reescriba mueveRect de modo que cree y devuelva un nuevo Rectangulo en lugar de modificar el viejo.

4. Comparativa y puntos clave

  • Copia superficial:
    • Crea un nuevo contenedor, pero no duplica objetos anidados.
    • Los cambios en objetos anidados se reflejan en la copia.
    • Se puede realizar con copy.copy(objeto) o objeto.copy().
  • Copia profunda:
  • Crea un nuevo contenedor y duplica recursivamente todos los objetos anidados.
  • Los cambios en objetos anidados no se reflejan en la copia.
  • Se realiza con copy.deepcopy(objeto).