05. Python: copia superficial y copia profunda

Publicado por

En la nota anterior mencioné el método copy() del tipo de dato diccionario, del cual dejé pendiente la explicación. En esta nota profundizaré en este método abordando la diferencia entre copia superficial y copia profunda.

En Python para los tipos de datos inmutables solamente se usan asignaciones y para los tipos de datos mutables además se utiliza la copia superficial y la copia profunda.

Asignación

Las variables en Python no guardan directamente valores ni objetos sino referencias a estos. Por lo que cuando se hace una asignación no se están copiando esos valores.

Ejemplo


>>> A = 5

>>> B = A

>>> id(A)

>>> id(B)

>>> A += 1

>>> id(A)

>>> id(B)

La función id() devuelve el identificador que Python le asigna a un objeto, en los siguientes diagramas cada celda representan un identificador diferente.

Salida:


>>> A = 5

>>> B = A

>>> id(A)

16888056

Copia superficial y profunda


>>> id(B)

16888056

Copia superficial y profunda


>>> A += 1

>>> id(A)

16888032

>>> id(B)

16888056

Copia superficial y profunda

En esta salida se puede ver que en el caso de los valores inmutables, Python manipula las referencias y no los valores.

Copia superficial y copia profunda

Veamos el siguiente script:


#!/usr/bin/python

from random import randint
from copy import copy, deepcopy

def lista(n=2):
    lst = list()
    for k in range(n):
        lst.append(randint(0,99))
return lst

LA = [lista(), lista()]

A = LA
B = copy(LA)
C = deepcopy(LA)

print "LA id =", id(LA), "->", LA
print "A id =", id(A), "->", A
print "B id =", id(B), "->", B
print "C id =", id(C), "->", C

print "LA[0] id =", id(LA[0]), "->", LA[0]
print "A[0] id =", id(A[0]), "->", A[0]
print "B[0] id =", id(B[0]), "->", B[0]
print "C[0] id =", id(C[0]), "->", C[0]

Como se puede observar el id de LA y A es el mismo ya que ambos hacen referencia a la misma instancia; el id de B es diferente porque este es una copia, sin embargo los id de sus listas interiores son iguales a los identificadores de las listas contenidas en LA, debido a que B es una copia superficial; en el caso de C, su id también es diferente al de A y LA, así como los identificadores de las listas que contiene, puesto que C es una copia profunda. A simple vista parece que la copia superficial hace exactamente lo mismo que la copia profunda porque los valores son iguales, sin embargo la diferencia radica en que las instancias a los objetos referenciados por A, B y C respectivamente son distintas. Se puede ver más claramente en el siguiente diagrama:

Copia superficial y profunda
Cada celda representa un identificador diferente.

En realidad, la copia superficial B es una instancia cuyos elementos apuntan a las instancias contenidas en la lista original LA, en cambio, C es una instancia cuyos elementos son referencias a instancias diferentes pero que contienen exactamente los mismos valores de LA. Esto se puede ver claramente en la salida:

LA[0] id = 140431967148584 -> [35, 56]
A[0] id = 140431967148584 -> [35, 56]
B[0] id = 140431967148584 -> [35, 56]
C[0] id = 140431967148296 -> [35, 56]

El id de LA[0] es el mismo que el de B[0], pero no que el de C[0], esto es porque son dos instancias diferentes que contienen los mismos valores.

Después de este ejemplo ya se pueden definir los conceptos.

Copia superficial

Solamente se copian las referencias a los elementos contenidos en el objeto.

Copia profunda

Si el objeto contiene subobjetos estos se copian recursivamente.

Método copy() y función deepcopy() con diccionarios (dict)

Como ya lo expliqué, el método copy() hace una copia superficial y para la copia profunda existe una implementación genérica aplicable a los diccionarios, esta es la función deepcopy(). El siguiente ejemplo muestra cómo se realiza con un tipo de dato mutable diccionario.

Ejemplo

Si al script anterior le agregamos las siguiente líneas de código:

d = {'lista':LA}
copias = [d.copy(), deepcopy(d)]
print copias[0]['lista'] is copias[1]['lista']
print LA is copias[0]['lista']
print LA is copias[1]['lista']

LA[0].append(8)

print A
print B
print C
print d
print copias

Se obtiene la siguiente salida:

False
True
False
[[35, 56, 8], [83, 22]]
[[35, 56, 8], [83, 22]]
[[35, 56], [83, 22]]
{'lista': [[35, 56, 8], [83, 22]]}
[{'lista': [[35, 56, 8], [83, 22]]}, {'lista': [[35, 56], [83, 22]]}]

Se puede observar que después de la instrucción LA[0].append(8) el elemento 0 de copias es distinto a su elemento 1, ya que en el primero, al ser una copia superficial hace referencia a la misma instancia que LA y en el segundo, al ser una copia profunda, hace referencia a una instancia distinta. Nota que la copia profunda sirve para manipular valores que se desean cambiar sin afectar el original.

Espero que con estos ejemplos te haya quedado más claro cómo Python realiza la copia superficial y la copia profunda, y sobre todo que hayas entendido sus diferencias. Si tienes alguna inquietud no dudes dejarla en los comentarios. ¡Hasta pronto!

4 comments

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *