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
¿Puede alguien explicar qué es exactamente lo que diferencia las copias? ¿Es algo relacionado con mutable & objetos inmutables? Si es así, ¿podría explicármelo?
Las operaciones normales de asignación simplemente apuntarán la nueva variable hacia el objeto existente. Los docs explican la diferencia entre copias superficiales y profundas:
La diferencia entre copia superficial y profunda sólo es relevante para
objetos compuestos (objetos que contienen otros objetos, como listas o instancias de clases):
- Una copia superficial construye un nuevo objeto compuesto y luego (en la medida de lo posible) inserta en él referencias a los objetos que se encuentran en el original. > Una copia profunda construye un nuevo objeto compuesto y luego, recursivamente, inserta en él copias de los objetos que se encuentran en el objeto original. original.
He aquí una pequeña demostración:
import copy
a = [1, 2, 3]
b = [4, 5, 6]
c = [a, b]
Usando operaciones normales de asignación 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]
Utilizando 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, así que Python usa los mismos datos; los ids son siempre los mismos. Para objetos mutables, ya que pueden cambiar potencialmente, la copia [superficial] crea un nuevo objeto.
La copia profunda está relacionada con las estructuras anidadas. Si tienes una lista de listas, entonces deepcopy copia
también las listas anidadas, por lo que es una copia recursiva. Con sólo copiar, tienes una nueva lista externa, pero las listas internas son referencias.
Assignment no copia. Simplemente establece la referencia a los datos antiguos. Así que necesitas copiar para crear una nueva lista con el mismo contenido.
a, b, c, d, a1, b1, c1 y d1 son referencias a objetos en memoria, que se identifican unívocamente por sus ids.
Una operación de asignación toma una referencia al objeto en memoria y asigna esa referencia a un nuevo nombre. c=[1,2,3,4]es una asignación que crea un nuevo objeto 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
. Como la lista es mutable, cualquier cosa que le ocurra a esa lista será visible independientemente de si accedes a ella a través de c
o c1
, porque ambas hacen referencia al mismo objeto.
c1=copia.copia(c)es una "copia superficial" que crea una nueva lista y asigna la referencia a la nueva lista a
c1. c
sigue apuntando a la lista original. Así, si modificas la lista en c1
, la lista a la que c
hace referencia no cambiará.
El concepto de copia es irrelevante para objetos inmutables como enteros y cadenas. Puesto que no puedes modificar esos objetos, nunca hay necesidad de tener dos copias del mismo valor en memoria en diferentes ubicaciones. Así que los enteros y las cadenas, y algunos otros objetos a los que no se aplica el concepto de copia, simplemente se reasignan. Por eso tus ejemplos con a
y b
dan como resultado ids idénticos.
c1=copia.copia.profunda(c)` es una "copia profunda", pero funciona igual que una copia superficial en este ejemplo. Las copias profundas difieren de las superficiales en que las copias superficiales harán una nueva copia del objeto en sí, pero cualquier referencia dentro de ese objeto no será copiada. En tu ejemplo, tu lista sólo tiene enteros en su interior (que son inmutables), y como se discutió anteriormente no hay necesidad de 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]]
Se trata de una lista que contiene otras listas (también podría describirse como una matriz bidimensional).
Si ejecutas un "shallow copy" sobre e
, copiándola a e1
, verás que el id de la lista cambia, pero cada copia de la lista contiene referencias a las mismas tres listas -- las listas con enteros dentro. Esto significa que si hicieras 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 hicieras 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 modificas las listas internas, puedes ver esos cambios independientemente de si las estás viendo a través de una copia o de la otra. Pero si modificas una de las listas externas como se ha dicho, entonces e
contiene tres referencias a las tres listas originales más una referencia a una nueva lista. Y e1
sigue conteniendo sólo las tres referencias originales.
Una 'copia profunda' no sólo duplicaría la lista exterior, sino que también iría dentro de las listas y duplicaría las listas interiores, de forma que los dos objetos resultantes no contienen ninguna de las mismas referencias (en lo que a objetos mutables se refiere). Si las listas internas tuvieran más listas (u otros objetos como diccionarios) en su interior, también se duplicarían. Esa es la parte 'profunda' de la 'copia profunda'.