Comprensión dict.copy (): ¿superficial o profunda?

429

Mientras lee la documentación dict.copy(), dice que hace una copia superficial del diccionario. Lo mismo ocurre con el libro que estoy siguiendo (Beazley's Python Reference), que dice:

El método m.copy () hace una copia superficial de los elementos contenidos en un objeto de mapeo y los coloca en un nuevo objeto de mapeo.

Considera esto:

>>> original = dict(a=1, b=2)
>>> new = original.copy()
>>> new.update({'c': 3})
>>> original
{'a': 1, 'b': 2}
>>> new
{'a': 1, 'c': 3, 'b': 2}

Así que supuse que esto actualizaría el valor de original(y agregaría 'c': 3) también porque estaba haciendo una copia superficial. Como si lo hicieras por una lista:

>>> original = [1, 2, 3]
>>> new = original
>>> new.append(4)
>>> new, original
([1, 2, 3, 4], [1, 2, 3, 4])

Esto funciona como se esperaba.

Dado que ambas son copias poco profundas, ¿por qué dict.copy()no funciona como esperaba? ¿O mi comprensión de la copia superficial o profunda es defectuosa?

usuario225312
fuente
2
Pintoresco que no explican "superficial". Conocimiento interno, guiño. Solo el dict y las claves son una copia, mientras que los dictados anidados dentro de ese primer nivel son referencias, por ejemplo, no se pueden eliminar en un bucle. Por lo tanto, dict.copy () de Python en ese caso no es útil ni intuitivo. Gracias por tu pregunta
gseattle

Respuestas:

991

Por "copia superficial" significa que el contenido del diccionario no se copia por valor, sino que simplemente crea una nueva referencia.

>>> a = {1: [1,2,3]}
>>> b = a.copy()
>>> a, b
({1: [1, 2, 3]}, {1: [1, 2, 3]})
>>> a[1].append(4)
>>> a, b
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})

Por el contrario, una copia profunda copiará todos los contenidos por valor.

>>> import copy
>>> c = copy.deepcopy(a)
>>> a, c
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})
>>> a[1].append(5)
>>> a, c
({1: [1, 2, 3, 4, 5]}, {1: [1, 2, 3, 4]})

Entonces:

  1. b = a: Asignación de referencia, Make ay bpuntos al mismo objeto.

    La ilustración de 'a = b': 'a' y 'b' apuntan a '{1: L}', 'L' apunta a '[1, 2, 3]'.

  2. b = a.copy(): Copia superficial, ay bse convertirán en dos objetos aislados, pero sus contenidos aún comparten la misma referencia

    La ilustración de 'b = a.copy ()': 'a' apunta a '{1: L}', 'b' apunta a '{1: M}', 'L' y 'M' apuntan a '[ 1, 2, 3] '.

  3. b = copy.deepcopy(a): Copia profunda, ay bla estructura y el contenido quedan completamente aislados.

    Ilustración de 'b = copy.deepcopy (a)': 'a' apunta a '{1: L}', 'L' apunta a '[1, 2, 3]';  'b' apunta a '{1: M}', 'M' apunta a una instancia diferente de '[1, 2, 3]'.

kennytm
fuente
Buena respuesta, pero podrías considerar corregir el error gramatical en tu primera oración. Y no hay razón para no Lvolver a usarlo b. Hacerlo simplificaría el ejemplo.
Tom Russell
@kennytm: ¿Cuál es la diferencia entre los dos primeros ejemplos, de hecho? Obtiene el mismo resultado, pero una implementación interna ligeramente diferente, pero ¿para qué importa?
JavaSa
@TomRussell: O cualquiera, ya que esta pregunta es bastante antigua, mi pregunta de aclaración es para todos
JavaSa
@JavaSa Importa si, por ejemplo, lo haces b[1][0] = 5. Si bes una copia superficial, acaba de cambiar a[1][0].
Tom Russell
2
Gran explicación, ¡realmente me salvó el día! Gracias ... ¿Se puede aplicar lo mismo a la lista, str y otros tipos de datos de python?
Bhuro
38

No es una cuestión de copia profunda o copia superficial, nada de lo que está haciendo es copia profunda.

Aquí:

>>> new = original 

está creando una nueva referencia a la lista / dict referenciada por el original.

mientras aquí:

>>> new = original.copy()
>>> # or
>>> new = list(original) # dict(original)

está creando una nueva lista / dict que se llena con una copia de las referencias de los objetos contenidos en el contenedor original.

Lie Ryan
fuente
31

Toma este ejemplo:

original = dict(a=1, b=2, c=dict(d=4, e=5))
new = original.copy()

Ahora cambiemos un valor en el nivel 'superficial' (primer):

new['a'] = 10
# new = {'a': 10, 'b': 2, 'c': {'d': 4, 'e': 5}}
# original = {'a': 1, 'b': 2, 'c': {'d': 4, 'e': 5}}
# no change in original, since ['a'] is an immutable integer

Ahora cambiemos un valor un nivel más profundo:

new['c']['d'] = 40
# new = {'a': 10, 'b': 2, 'c': {'d': 40, 'e': 5}}
# original = {'a': 1, 'b': 2, 'c': {'d': 40, 'e': 5}}
# new['c'] points to the same original['d'] mutable dictionary, so it will be changed
eumiro
fuente
8
no change in original, since ['a'] is an immutable integerEsta. En realidad responde a la pregunta formulada.
CivFan
8

Agregando a la respuesta de kennytm. Cuando hace una copia superficial parent.copy (), se crea un nuevo diccionario con las mismas claves, pero los valores no se copian, se hace referencia a ellos. Si agrega un nuevo valor a parent_copy , no afectará a parent porque parent_copy es un diccionario nuevo. No referencia.

parent = {1: [1,2,3]}
parent_copy = parent.copy()
parent_reference = parent

print id(parent),id(parent_copy),id(parent_reference)
#140690938288400 140690938290536 140690938288400

print id(parent[1]),id(parent_copy[1]),id(parent_reference[1])
#140690938137128 140690938137128 140690938137128

parent_copy[1].append(4)
parent_copy[2] = ['new']

print parent, parent_copy, parent_reference
#{1: [1, 2, 3, 4]} {1: [1, 2, 3, 4], 2: ['new']} {1: [1, 2, 3, 4]}

El valor hash (id) de parent [1] , parent_copy [1] es idéntico, lo que implica [1,2,3] de parent [1] y parent_copy [1] almacenados en id 140690938288400.

Pero el hash de parent y parent_copy son diferentes, lo que implica que son diccionarios diferentes y parent_copy es un nuevo diccionario que tiene valores de referencia a los valores de parent

Vkreddy Komatireddy
fuente
5

"nuevo" y "original" son dictos diferentes, por eso puede actualizar solo uno de ellos. Los elementos se copian a poca profundidad, no el dict en sí.

Joril
fuente
2

El contenido se copia poco profundo.

Entonces, si el original dictcontiene uno listu otro dictionary, si modifica uno de ellos en el original o su copia superficial los modificará (el listo el dict) en el otro.

Cazador de la selva
fuente
1

En su segunda parte, debe usar new = original.copy()

.copyY =son cosas diferentes.

朱骏 杰
fuente