¿Cuál es exactamente la diferencia entre la copia de poca profundidad, la copia en profundidad y la 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 deepcopy –

 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é hace exactamente una diferencia entre las copias? ¿Es algo relacionado con objetos mutables e inmutables? Si es así, ¿puede por favor explicármelo?

Las operaciones de asignación normal simplemente apuntarán la nueva variable hacia el objeto existente. Los documentos explican la diferencia entre copias poco profundas 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 en él a los objetos que se encuentran en el original.

  • Una copia profunda construye un nuevo objeto compuesto y luego, recursivamente, inserta copias en él de los objetos que se encuentran 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 normal 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 hay necesidad de copiar porque los datos nunca cambiarán, por lo que Python usa los mismos datos; Las identificaciones son siempre las mismas. Para objetos mutables, ya 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, copies profundidad 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 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 la assignment objetos mutables, la copy y el deepcopy comportan de manera diferente. Vamos a hablar de cada uno de ellos con ejemplos.

Una operación de asignación simplemente asigna la referencia de origen a 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. Así que ahora los cambios a la variable original no se reflejarán en la variable de copia y viceversa. Sin embargo, la copy(shallow copy) , no crea una copia de objetos nesteds, sino que simplemente copia la referencia de los objetos nesteds. Deepcopy copia todos los objetos nesteds recursivamente.

Algunos ejemplos para demostrar el comportamiento de copy y copy 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] #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 

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) 

introduzca la descripción de la imagen aquí

a, b, c, d, a1, b1, c1 y d1 son referencias a objetos en memoria, que se identifican 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 la asigna a c1 . Dado que la lista es mutable, cualquier cosa que suceda en esa lista será visible independientemente de si accede a ella a través de c o c1 , porque 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 todavía apunta a la lista original. Por lo tanto, si modifica la lista en c1 , la lista a la que c refiere no cambiará.

El concepto de copia 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. Así, 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 que 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 no se copiarán las referencias dentro de ese objeto. En su ejemplo, su lista solo tiene enteros (que son inmutables), y como se explicó anteriormente, no es necesario copiarlos. Así que 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 , al copiarla 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 números enteros dentro. Eso significa que si tuviera que hacer e[0].append(3) , 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 hizo e.append([10, 11, 12]) , e sería [[1, 2, 3],[4, 5, 6],[7, 8, 9],[10, 11, 12]] . Pero e1 aún sería [[1, 2, 3],[4, 5, 6],[7, 8, 9]] . Esto se debe a que las listas externas son objetos separados que inicialmente cada una contiene 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 anteriormente, entonces 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 contengan ninguna de las mismas referencias (en lo que respecta a los objetos mutables) . Si las listas internas tuvieran más listas (u otros objetos, como diccionarios) dentro de ellas, también se duplicarían. Esa es la parte ‘profunda’ de la ‘copia profunda’.

En python, cuando asignamos objetos como lista, tuplas, dict, etc. a otro objeto generalmente con un signo ‘=’, python crea copias por referencia . Es decir, digamos que tenemos una lista de esta lista:

 list1 = [ [ 'a' , 'b' , 'c' ] , [ 'd' , 'e' , 'f' ] ] 

y asignamos otra lista a esta lista como:

 list2 = list1 

Luego, si imprimimos list2 en el terminal 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 resultará en cambios visibles en ambos objetos, es decir, ambos objetos apuntan a la misma ubicación de memoria. Si cambiamos list1 de esta manera:

 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 que viene a Copia superficial , cuando dos objetos se copian a través de una copia superficial, el objeto secundario de ambos objetos principales se refiere a la misma ubicación de memoria, pero cualquier cambio nuevo en cualquiera de los objetos copiados será independiente entre sí. Entendamos 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 '] ] 

note, list2 no se ve afectado, pero si hacemos cambios en objetos secundarios como:

 list1[0][0] = 'x' 

entonces tanto list1 como list2 obtendrán un cambio:

 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 entre sí. Si se copian dos objetos a través de Deep Copy, ambos padres e hijo estarán apuntando a 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 '] ] 

note, list2 no se ve afectado, pero si hacemos cambios en objetos secundarios como:

 list1[0][0] = 'x' 

entonces también list2 no se verá afectado ya que todos los objetos secundarios y el objeto primario 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.

El siguiente código muestra la diferencia entre la asignación, la copia superficial con el método de copia, la copia superficial con la (sección) [:] y la copia en profundidad. El siguiente ejemplo utiliza listas anidadas allí haciendo más evidentes las diferencias.

 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])) 

El GIST que se debe tomar es el siguiente: tratar con listas poco profundas (sin listas secundarias, solo elementos individuales) al usar “asignación normal” se eleva un “efecto secundario” cuando crea una lista superficial y luego crea una copia de esta lista con “asignación normal” . Este “efecto secundario” se produce cuando cambia cualquier elemento de la lista de copia creada, porque cambiará automáticamente los mismos elementos de la lista original. Esto es cuando la copy es útil, ya que no cambiará los elementos de la lista original al cambiar los elementos de la copia.

Por otro lado, la copy tiene un “efecto secundario”, cuando tienes una lista que contiene listas (sub_lists) y deepcopy resuelve. Por ejemplo, si crea una lista grande que tiene listas anidadas (sublistas) y crea una copia de esta lista grande (la lista original). El “efecto secundario” surgiría cuando modificara las sublistas de la lista de copias, lo que modificaría automáticamente las sublistas de la lista grande. A veces (en algunos proyectos) desea mantener la lista grande (su lista original) tal como está sin modificación, y todo lo que desea es hacer una copia de sus elementos (sub_lists). Para eso, su solución es utilizar deepcopy que se encargará 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 solo se refieren a objetos compuestos (es decir, objetos que contienen otros objetos, como listas).

Aquí están las diferencias ilustradas en este ejemplo de código simple:

primero

veamos cómo se comporta la copy (superficial), 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, hagamos 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 diferentes direcciones

 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 

Los subelementos 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 original_list NO modifica los elementos de copy_list

 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] 

modificando sub_elementos original_list modificamos automáticamente sub_elements copy_list

 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] 

la modificación de copy_list sub_elements modifica automáticamente la lista original de 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, hagamos 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 diferentes direcciones

 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 

Los subelementos de original_list y copy_list tienen direcciones diferentes

 print(hex(id(original_list[5])), hex(id(copy_list[5]))) # 0x24eef08 0x24f3300 

la modificación de los elementos de original_list NO modifica los elementos de copy_list

 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 original_list NO modifica los sub_elementos copy_list

 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 sub_elementos de copy_list NO modifica los sub_elements de original_list

 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] 
 >>lst=[1,2,3,4,5] 

a = lst

b = lst [:]

b [1, 2, 3, 4, 5]

a [1, 2, 3, 4, 5]

lst es b falso

lst es un verdadero

id (lst) 46263192

id (a) 46263192 ——> Ver aquí id de a y id de lst es el mismo, así que su llamada copia profunda e incluso la respuesta booleana es verdadera

id (b) 46263512 ——> Ver aquí id de b e id de lst no es lo mismo, por lo que su llamada copia superficial e incluso la respuesta booleana es falsa, aunque la salida es la misma.