¿Cuál es la diferencia entre copia superficial, copia profunda y operación de asignación normal?
import copy
a = "deepak"
b = 1, 2, 3, 4
c = [1, 2, 3, 4]
d = {1: 10, 2: 20, 3: 30}
a1 = copy.copy(a)
b1 = copy.copy(b)
c1 = copy.copy(c)
d1 = copy.copy(d)
print("immutable - id(a)==id(a1)", id(a) == id(a1))
print("immutable - id(b)==id(b1)", id(b) == id(b1))
print("mutable - id(c)==id(c1)", id(c) == id(c1))
print("mutable - id(d)==id(d1)", id(d) == id(d1))
Obtengo los siguientes resultados:
immutable - id(a)==id(a1) True
immutable - id(b)==id(b1) True
mutable - id(c)==id(c1) False
mutable - id(d)==id(d1) False
Si realizo una copia profunda:
a1 = copy.deepcopy(a)
b1 = copy.deepcopy(b)
c1 = copy.deepcopy(c)
d1 = copy.deepcopy(d)
los resultados son los mismos:
immutable - id(a)==id(a1) True
immutable - id(b)==id(b1) True
mutable - id(c)==id(c1) False
mutable - id(d)==id(d1) False
Si trabajo en operaciones de asignación:
a1 = a
b1 = b
c1 = c
d1 = d
entonces los resultados son:
immutable - id(a)==id(a1) True
immutable - id(b)==id(b1) True
mutable - id(c)==id(c1) True
mutable - id(d)==id(d1) True
¿Alguien puede explicar qué marca exactamente la diferencia entre las copias? ¿Es algo relacionado con objetos mutables e inmutables? Si es así, ¿podrías explicármelo?
Las operaciones de asignación normales simplemente apuntarán la nueva variable hacia el objeto existente. Los documentos explican la diferencia entre copias superficiales y profundas:
La diferencia entre copia superficial y profunda solo es relevante para objetos compuestos (objetos que contienen otros objetos, como listas o instancias de clase):
Una copia superficial construye un nuevo objeto compuesto y luego (en la medida de lo posible) inserta referencias a los objetos encontrados en el original.
Una copia profunda construye un nuevo objeto compuesto y luego, de forma recursiva, inserta copias de los objetos encontrados en el original.
Aquí hay una pequeña demostración:
import copy
a = [1, 2, 3]
b = [4, 5, 6]
c = [a, b]
Usando operaciones de asignación normales para copiar:
d = c
print id(c) == id(d) # True - d is the same object as c
print id(c[0]) == id(d[0]) # True - d[0] is the same object as c[0]
Usando una copia superficial:
d = copy.copy(c)
print id(c) == id(d) # False - d is now a new object
print id(c[0]) == id(d[0]) # True - d[0] is the same object as c[0]
Usando una copia profunda:
d = copy.deepcopy(c)
print id(c) == id(d) # False - d is now a new object
print id(c[0]) == id(d[0]) # False - d[0] is now a new object
Para objetos inmutables, no es necesario copiarlos porque los datos nunca cambiarán, por lo que Python usa los mismos datos; Los identificadores son siempre los mismos. Para objetos mutables, dado que potencialmente pueden cambiar, la copia [superficial] crea un nuevo objeto.
La copia profunda está relacionada con estructuras anidadas. Si tiene una lista de listas, copie en profundidad copies
también las listas anidadas, por lo que es una copia recursiva. Con solo copiar, tiene una nueva lista externa, pero las listas internas son referencias.
La tarea no se copia. Simplemente establece la referencia a los datos antiguos. Entonces necesitas copiar para crear una nueva lista con el mismo contenido.
Para objetos inmutables, crear una copia no tiene mucho sentido ya que no van a cambiar. Para objetos mutables, assignment
y copy
se deepcopy
comportan de manera diferente. Hablemos de cada uno de ellos con ejemplos.
Una operación de asignación simplemente asigna la referencia del origen al destino, por ejemplo:
>>> i = [1,2,3]
>>> j=i
>>> hex(id(i)), hex(id(j))
>>> ('0x10296f908', '0x10296f908') #Both addresses are identical
Ahora i
y j
técnicamente nos referimos a la misma lista. Ambos i
y j
tienen la misma dirección de memoria. Cualquier actualización de uno de ellos se reflejará en el otro, por ejemplo:
>>> i.append(4)
>>> j
>>> [1,2,3,4] #Destination is updated
>>> j.append(5)
>>> i
>>> [1,2,3,4,5] #Source is updated
Por otro lado, copy
y deepcopy
crea una nueva copia de la variable. Entonces, ahora los cambios en la variable original no se reflejarán en la variable de copia y viceversa. Sin embargo, copy
( copia superficial ) no crea una copia de los objetos anidados, sino que simplemente copia las referencias a los objetos anidados, mientras que deepcopy
( copia profunda ) copia todos los objetos anidados de forma recursiva.
Algunos ejemplos para demostrar el comportamiento de copy
y deepcopy
:
Ejemplo de lista plana usando copy
:
>>> import copy
>>> i = [1,2,3]
>>> j = copy.copy(i)
>>> hex(id(i)), hex(id(j))
>>> ('0x102b9b7c8', '0x102971cc8') #Both addresses are different
>>> i.append(4)
>>> j
>>> [1,2,3] #Update of original list didn't affect the copied variable
Ejemplo de lista anidada usando copy
:
>>> import copy
>>> i = [1,2,3,[4,5]]
>>> j = copy.copy(i)
>>> hex(id(i)), hex(id(j))
>>> ('0x102b9b7c8', '0x102971cc8') #Both addresses are still different
>>> hex(id(i[3])), hex(id(j[3]))
>>> ('0x10296f908', '0x10296f908') #Nested lists have the same address
>>> i[3].append(6)
>>> j
>>> [1,2,3,[4,5,6]] #Update of original nested list updated the copy as well
Ejemplo de lista plana usando deepcopy
:
>>> import copy
>>> i = [1,2,3]
>>> j = copy.deepcopy(i)
>>> hex(id(i)), hex(id(j))
>>> ('0x102b9b7c8', '0x102971cc8') #Both addresses are different
>>> i.append(4)
>>> j
>>> [1,2,3] #Update of original list didn't affect the copied variable
Ejemplo de lista anidada usando deepcopy
:
>>> import copy
>>> i = [1,2,3,[4,5]]
>>> j = copy.deepcopy(i)
>>> hex(id(i)), hex(id(j))
>>> ('0x102b9b7c8', '0x102971cc8') #Both addresses are still different
>>> hex(id(i[3])), hex(id(j[3]))
>>> ('0x10296f908', '0x102b9b7c8') #Nested lists have different addresses
>>> i[3].append(6)
>>> j
>>> [1,2,3,[4,5]] #Update of original nested list didn't affect the copied variable
Veamos en un ejemplo gráfico cómo se ejecuta el siguiente código:
import copy
class Foo(object):
def __init__(self):
pass
a = [Foo(), Foo()]
shallow = copy.copy(a)
deep = copy.deepcopy(a)