python - lenguaje - ¿Cuál es exactamente la diferencia entre la copia superficial, la copia en profundidad y la operación de asignación normal?
python tutorial (8)
A continuación, el código muestra la diferencia entre la asignación, la copia superficial con el método de copia, la copia superficial con la (división) [:] y la copia en profundidad. El ejemplo siguiente usa listas anidadas al hacer las diferencias más evidentes.
from copy import deepcopy
########"List assignment (does not create a copy) ############
l1 = [1,2,3, [4,5,6], [7,8,9]]
l1_assigned = l1
print(l1)
print(l1_assigned)
print(id(l1), id(l1_assigned))
print(id(l1[3]), id(l1_assigned[3]))
print(id(l1[3][0]), id(l1_assigned[3][0]))
l1[3][0] = 100
l1.pop(4)
l1.remove(1)
print(l1)
print(l1_assigned)
print("###################################")
########"List copy using copy method (shallow copy)############
l2 = [1,2,3, [4,5,6], [7,8,9]]
l2_copy = l2.copy()
print(l2)
print(l2_copy)
print(id(l2), id(l2_copy))
print(id(l2[3]), id(l2_copy[3]))
print(id(l2[3][0]), id(l2_copy[3][0]))
l2[3][0] = 100
l2.pop(4)
l2.remove(1)
print(l2)
print(l2_copy)
print("###################################")
########"List copy using slice (shallow copy)############
l3 = [1,2,3, [4,5,6], [7,8,9]]
l3_slice = l3[:]
print(l3)
print(l3_slice)
print(id(l3), id(l3_slice))
print(id(l3[3]), id(l3_slice[3]))
print(id(l3[3][0]), id(l3_slice[3][0]))
l3[3][0] = 100
l3.pop(4)
l3.remove(1)
print(l3)
print(l3_slice)
print("###################################")
########"List copy using deepcopy ############
l4 = [1,2,3, [4,5,6], [7,8,9]]
l4_deep = deepcopy(l4)
print(l4)
print(l4_deep)
print(id(l4), id(l4_deep))
print(id(l4[3]), id(l4_deep[3]))
print(id(l4[3][0]), id(l4_deep[3][0]))
l4[3][0] = 100
l4.pop(4)
l4.remove(1)
print(l4)
print(l4_deep)
print("##########################")
print(l4[2], id(l4[2]))
print(l4_deep[3], id(l4_deep[3]))
print(l4[2][0], id(l4[2][0]))
print(l4_deep[3][0], id(l4_deep[3][0]))
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 exactamente qué hace la diferencia entre las copias? ¿Es algo relacionado con objetos mutables e inmutables? Si es así, ¿puedes explicarme por favor?
El GIST a tomar es este: tratar con listas poco profundas (sin sublistas, solo elementos individuales) usando "asignación normal" aumenta un "efecto secundario" cuando se crea una lista superficial y luego se crea una copia de esta lista usando "asignación normal" . Este "efecto secundario" es cuando cambia cualquier elemento de la lista de copias creada, ya que cambiará automáticamente los mismos elementos de la lista original. Es entonces cuando la copy
es útil, ya que no cambiará los elementos de la lista original al cambiar los elementos de copia.
Por otro lado, copy
tiene un "efecto secundario" cuando tienes una lista que tiene listas (sublistas) y deepcopy
resuelve. Por ejemplo, si crea una lista grande que tiene listas anidadas en ella (sublistas), y crea una copia de esta gran lista (la lista original). El "efecto secundario" surgiría cuando modifiques las sublistas de la lista de copias, lo que automáticamente modificaría las sublistas de la lista grande. A veces (en algunos proyectos) desea mantener la lista grande (su lista original) tal como está sin modificaciones, y todo lo que desea es hacer una copia de sus elementos (sublistas). Para eso, su solución es usar deepcopy
que se ocupará de este "efecto secundario" y realizará una copia sin modificar el contenido original.
Los diferentes comportamientos de las operaciones de copy
y copy
deep copy
conciernen solo a objetos compuestos (es decir, objetos que contienen otros objetos, como listas).
Estas son las diferencias ilustradas en este sencillo ejemplo de código:
primero
veamos cómo se comporta copy
(shallow), creando una lista original y una copia de esta lista:
import copy
original_list = [1, 2, 3, 4, 5, [''a'', ''b'']]
copy_list = copy.copy(original_list)
Ahora, ejecutemos algunas pruebas de print
y veamos cómo se comporta la lista original en comparación con su lista de copias:
original_list y copy_list tienen direcciones diferentes
print(hex(id(original_list)), hex(id(copy_list))) # 0x1fb3030 0x1fb3328
los elementos de original_list y copy_list tienen las mismas direcciones
print(hex(id(original_list[1])), hex(id(copy_list[1]))) # 0x537ed440 0x537ed440
sub_elements de original_list y copy_list tienen las mismas direcciones
print(hex(id(original_list[5])), hex(id(copy_list[5]))) # 0x1faef08 0x1faef08
la modificación de los elementos de la lista original NO modifica los elementos de la lista de copias
original_list.append(6)
print("original_list is:", original_list) # original_list is: [1, 2, 3, 4, 5, [''a'', ''b''], 6]
print("copy_list is:", copy_list) # copy_list is: [1, 2, 3, 4, 5, [''a'', ''b'']]
la modificación de los elementos copy_list NO modifica los elementos original_list
copy_list.append(7)
print("original_list is:", original_list) # original_list is: [1, 2, 3, 4, 5, [''a'', ''b''], 6]
print("copy_list is:", copy_list) # copy_list is: [1, 2, 3, 4, 5, [''a'', ''b''], 7]
modificar los sub_elementos de la lista original modificar automáticamente los subelementos de la lista de copias
original_list[5].append(''c'')
print("original_list is:", original_list) # original_list is: [1, 2, 3, 4, 5, [''a'', ''b'', ''c''], 6]
print("copy_list is:", copy_list) # copy_list is: [1, 2, 3, 4, 5, [''a'', ''b'', ''c''], 7]
modificando copy_list sub_elements modifica automáticamente original_list sub_elements
copy_list[5].append(''d'')
print("original_list is:", original_list) # original_list is: [1, 2, 3, 4, 5, [''a'', ''b'', ''c'', ''d''], 6]
print("copy_list is:", copy_list) # copy_list is: [1, 2, 3, 4, 5, [''a'', ''b'', ''c'', ''d''], 7]
Segundo
veamos cómo se comporta deepcopy
, haciendo lo mismo que hicimos con copy
(creando una lista original y una copia de esta lista):
import copy
original_list = [1, 2, 3, 4, 5, [''a'', ''b'']]
copy_list = copy.copy(original_list)
Ahora, ejecutemos algunas pruebas de print
y veamos cómo se comporta la lista original en comparación con su lista de copias:
import copy
original_list = [1, 2, 3, 4, 5, [''a'', ''b'']]
copy_list = copy.deepcopy(original_list)
original_list y copy_list tienen direcciones diferentes
print(hex(id(original_list)), hex(id(copy_list))) # 0x1fb3030 0x1fb3328
los elementos de original_list y copy_list tienen las mismas direcciones
print(hex(id(original_list[1])), hex(id(copy_list[1]))) # 0x537ed440 0x537ed440
sub_elements de original_list y copy_list tienen diferentes direcciones
print(hex(id(original_list[5])), hex(id(copy_list[5]))) # 0x24eef08 0x24f3300
la modificación de los elementos de la lista original NO modifica los elementos de la lista de copias
original_list.append(6)
print("original_list is:", original_list) # original_list is: [1, 2, 3, 4, 5, [''a'', ''b''], 6]
print("copy_list is:", copy_list) # copy_list is: [1, 2, 3, 4, 5, [''a'', ''b'']]
la modificación de los elementos copy_list NO modifica los elementos original_list
copy_list.append(7)
print("original_list is:", original_list) # original_list is: [1, 2, 3, 4, 5, [''a'', ''b''], 6]
print("copy_list is:", copy_list) # copy_list is: [1, 2, 3, 4, 5, [''a'', ''b''], 7]
la modificación de sub_elements de la lista original NO modifica sub_elements de la lista de copias
original_list[5].append(''c'')
print("original_list is:", original_list) # original_list is: [1, 2, 3, 4, 5, [''a'', ''b'', ''c''], 6]
print("copy_list is:", copy_list) # copy_list is: [1, 2, 3, 4, 5, [''a'', ''b''], 7]
la modificación de los subelementos copy_list NO modifica sub_elements de la lista original
copy_list[5].append(''d'')
print("original_list is:", original_list) # original_list is: [1, 2, 3, 4, 5, [''a'', ''b'', ''c'', ''d''], 6]
print("copy_list is:", copy_list) # copy_list is: [1, 2, 3, 4, 5, [''a'', ''b'', ''d''], 7]
En python, cuando asignamos objetos como list, tuples, dict, etc. a otro objeto usualmente con un signo ''='', python crea copias por referencia . Es decir, digamos que tenemos una lista de listas como esta:
list1 = [ [ ''a'' , ''b'' , ''c'' ] , [ ''d'' , ''e'' , ''f'' ] ]
y asignamos otra lista a esta lista como:
list2 = list1
entonces si imprimimos list2 en el terminal de python obtendremos esto:
list2 = [ [ ''a'', ''b'', ''c''] , [ ''d'', ''e'', '' f ''] ]
Tanto list1 como list2 apuntan a la misma ubicación de memoria, cualquier cambio en cualquiera de ellos dará como resultado cambios visibles en ambos objetos, es decir, ambos objetos apuntan a la misma ubicación de memoria. Si cambiamos list1 así:
list1[0][0] = ''x’
list1.append( [ ''g''] )
entonces tanto list1 como list2 serán:
list1 = [ [ ''x'', ''b'', ''c''] , [ ''d'', ''e'', '' f ''] , [ ''g''] ]
list2 = [ [ ''x'', ''b'', ''c''] , [ ''d'', ''e'', '' f ''] , [ ''g’ ] ]
Ahora en Copia superficial , cuando dos objetos se copian mediante una copia superficial, el objeto hijo de ambos objetos principales se refiere a la misma ubicación de memoria, pero cualquier cambio nuevo en el objeto copiado será independiente entre sí. Comprendamos esto con un pequeño ejemplo. Supongamos que tenemos este pequeño fragmento de código:
import copy
list1 = [ [ ''a'', ''b'', ''c''] , [ ''d'', ''e'', '' f ''] ] # assigning a list
list2 = copy.copy(list1) # shallow copy is done using copy function of copy module
list1.append ( [ ''g'', ''h'', ''i''] ) # appending another list to list1
print list1
list1 = [ [ ''a'', ''b'', ''c''] , [ ''d'', ''e'', '' f ''] , [ ''g'', ''h'', ''i''] ]
list2 = [ [ ''a'', ''b'', ''c''] , [ ''d'', ''e'', '' f ''] ]
aviso, list2 no se ve afectado, pero si hacemos cambios a los objetos secundarios como:
list1[0][0] = ''x’
entonces tanto list1 como list2 obtendrán cambios:
list1 = [ [ ''x'', ''b'', ''c''] , [ ''d'', ''e'', '' f ''] , [ ''g'', ''h'', ''i''] ]
list2 = [ [ ''x'', ''b'', ''c''] , [ ''d'', ''e'', '' f ''] ]
Ahora, la copia profunda ayuda a crear objetos completamente aislados el uno del otro. Si se copian dos objetos a través de Deep Copy, tanto el padre como su hijo señalarán una ubicación de memoria diferente. Ejemplo:
import copy
list1 = [ [ ''a'', ''b'', ''c''] , [ ''d'', ''e'', '' f ''] ] # assigning a list
list2 = deepcopy.copy(list1) # deep copy is done using deepcopy function of copy module
list1.append ( [ ''g'', ''h'', ''i''] ) # appending another list to list1
print list1
list1 = [ [ ''a'', ''b'', ''c''] , [ ''d'', ''e'', '' f ''] , [ ''g'', ''h'', ''i''] ]
list2 = [ [ ''a'', ''b'', ''c''] , [ ''d'', ''e'', '' f ''] ]
aviso, list2 no se ve afectado, pero si hacemos cambios a los objetos secundarios como:
list1[0][0] = ''x’
entonces también list2 no se verá afectado ya que todos los objetos secundarios y el objeto principal apuntan a una ubicación de memoria diferente:
list1 = [ [ ''x'', ''b'', ''c''] , [ ''d'', ''e'', '' f ''] , [ ''g'', ''h'', ''i''] ]
list2 = [ [ ''a'', ''b'', ''c''] , [ ''d'', ''e'', '' f '' ] ]
Espero eso ayude.
Las operaciones de asignación normal simplemente señalarán la nueva variable hacia el objeto existente. Los docs explican la diferencia entre copias superficiales y profundas:
La diferencia entre la 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 en él a los objetos encontrados en el original.
Una copia profunda construye un nuevo objeto compuesto y luego, recursivamente, 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, crear una copia no tiene mucho sentido ya que no van a cambiar. Para la assignment
objetos mutables, copy
y deepcopy
comporta de manera diferente. Vamos a hablar sobre cada uno de ellos con ejemplos.
Una operación de asignación simplemente asigna la referencia de la fuente 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 se refiere a la misma lista. Tanto i
como j
tienen la misma dirección de memoria. Cualquier actualización a cualquiera de ellos se reflejará en el otro. p.ej:
>>> 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, los cambios a la variable original no se reflejarán en la variable de copia y viceversa. Sin embargo, copy(shallow copy)
, no crea una copia de objetos anidados, en su lugar solo copia la referencia de objetos anidados. Deepcopy copia todos los objetos anidados recursivamente.
Algunos ejemplos para demostrar el comportamiento de copy
y deepcopy
:
Ejemplo de lista plana con 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] #Updation of original list didn''t affected 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 same address
>>> i[3].append(6)
>>> j
>>> [1,2,3,[4,5,6]] #Updation 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] #Updation of original list didn''t affected 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]] #Updation of original nested list didn''t affected the copied variable
Para objetos inmutables, no hay necesidad de copiar porque los datos nunca cambiarán, por lo que Python usa los mismos datos; Los identificadores son siempre lo mismo. Para objetos mutables, ya que pueden cambiar potencialmente, la copia [poco profunda] crea un nuevo objeto.
La copia profunda está relacionada con estructuras anidadas. Si tiene una lista de listas, Deepcopy 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 asignación no se copia. Simplemente establece la referencia a los datos antiguos. Entonces necesita copiar para crear una nueva lista con los mismos contenidos.
a, b, c, d, a1, b1, c1 y d1 son referencias a objetos en la memoria, que están identificados de forma única por sus identificadores.
Una operación de asignación toma una referencia al objeto en la memoria y asigna esa referencia a un nuevo nombre. c=[1,2,3,4]
es una asignación que crea un nuevo objeto de lista que contiene esos cuatro enteros, y asigna la referencia a ese objeto a c
. c1=c
es una asignación que toma la misma referencia al mismo objeto y lo asigna a c1
. Como la lista es mutable, cualquier cosa que suceda con esa lista estará visible independientemente de si accede a ella a través de c
o c1
, ya que ambos hacen referencia al mismo objeto.
c1=copy.copy(c)
es una "copia superficial" que crea una nueva lista y asigna la referencia a la nueva lista a c1
. c
aún apunta a la lista original. Entonces, si modifica la lista en c1
, la lista a la que c
refiere no cambiará.
El concepto de copiar es irrelevante para objetos inmutables como enteros y cadenas. Como no puede modificar esos objetos, nunca es necesario tener dos copias del mismo valor en la memoria en diferentes ubicaciones. Por lo tanto, los números enteros y las cadenas, y algunos otros objetos a los que no se aplica el concepto de copia, simplemente se reasignan. Esta es la razón por la cual sus ejemplos con b
resultan en identificaciones idénticas.
c1=copy.deepcopy(c)
es una "copia profunda", pero funciona igual que una copia superficial en este ejemplo. Las copias profundas difieren de las copias superficiales en que las copias superficiales harán una nueva copia del objeto en sí, pero las referencias dentro de ese objeto no se copiarán. En su ejemplo, su lista solo contiene números enteros (que son inmutables), y como se discutió anteriormente, no es necesario copiarlos. Entonces, la parte "profunda" de la copia profunda no se aplica. Sin embargo, considere esta lista más compleja:
e = [[1, 2],[4, 5, 6],[7, 8, 9]]
Esta es una lista que contiene otras listas (también podría describirla como una matriz bidimensional).
Si ejecuta una "copia superficial" en e
, copiándola en e1
, encontrará que la identificación de la lista cambia, pero cada copia de la lista contiene referencias a las mismas tres listas: las listas con enteros dentro. Eso significa que si tuviera que hacer e[0].append(3)
, entonces e
sería [[1, 2, 3],[4, 5, 6],[7, 8, 9]]
. Pero e1
también sería [[1, 2, 3],[4, 5, 6],[7, 8, 9]]
. Por otro lado, si posteriormente e.append([10, 11, 12])
, e
sería [[1, 2, 3],[4, 5, 6],[7, 8, 9],[10, 11, 12]]
. Pero e1
seguiría siendo [[1, 2, 3],[4, 5, 6],[7, 8, 9]]
. Esto se debe a que las listas externas son objetos separados que inicialmente contienen tres referencias a tres listas internas. Si modifica las listas internas, puede ver esos cambios sin importar si los está viendo a través de una copia u otra. Pero si modifica una de las listas externas como arriba, e
contiene tres referencias a las tres listas originales más una referencia más a una nueva lista. Y e1
todavía solo contiene las tres referencias originales.
Una ''copia profunda'' no solo duplicaría la lista externa, sino que también iría dentro de las listas y duplicaría las listas internas, de modo que los dos objetos resultantes no contienen ninguna de las mismas referencias (en lo que respecta a los objetos mutables). . Si las listas internas tuvieran listas adicionales (u otros objetos como diccionarios) dentro de ellas, también se duplicarían. Esa es la parte "profunda" de la "copia profunda".