Estaba usando listas en un programa, pero no entiendo el siguiente comportamiento. He comenzado a comprender la mutabilidad y cómo afecta la asignación de variables, pero no veo el problema aquí:

class Test:
    def __init__(self, list_n):
        list_a = list_n[:]
        list_b = list_n[:]
        print(list_a is list_b) # Prints False
        print(list_a is list_n) # Prints False
        print(list_b is list_n) # Prints False

        list_a[0][0] = 1
        print(list_a) # Both of these print [[1,0,0][0,0,0][0,0,0]]
        print(list_b)

def main():
    list_n = [[0,0,0],[0,0,0],[0,0,0]]
    test = Test(list_n)      

if __name__ == '__main__': main()

Tanto list_a como list_b parecen seguir apuntando a la misma lista, aunque pensé que había tomado las medidas necesarias para evitar que eso sucediera.

2
Paul Morenkov 14 ene. 2017 a las 02:11
6
Un segmento de lista es una copia superficial .
 – 
jonrsharpe
14 ene. 2017 a las 02:18
3
Solo para que conste, la mutabilidad no afecta la asignación .
 – 
juanpa.arrivillaga
14 ene. 2017 a las 02:19
 – 
nbro
14 ene. 2017 a las 02:47

1 respuesta

La mejor respuesta

¿Por qué no funcionó el uso de la notación de sectores para copiar mi lista?

La razón por la que su ejemplo no funcionó es porque solo hizo una copia superficial de list_n. Una copia superficial de una lista solo copia la lista de "nivel superior". La copia superficial de una lista no copia las sublistas . Se hacen copias superficiales de listas llamando a copy() en list (list.copy()) o usando notación de sector (list[:]).

En el nivel C, cuando se realiza una copia superficial en elementos que son listas, los punteros a las listas, llamados PyObject s, se copian de una lista a otra. Sin embargo, el puntero real para cada sublista no se copia y, por lo tanto, list_a y list_b contienen punteros a las mismas sublistas exactas .

Para decirlo claramente, nunca hizo una copia de cada sublista en list_n y, por lo tanto, list_a y list_b todavía contienen punteros a las mismas sublistas. Esto se puede solucionar creando una "copia profunda" de list_n - una copia de cada sublista en la lista original sin importar el nivel de anidación - usando copy.deepcopy():

>>> from copy import deepcopy
>>> 
>>> list_n = [[0,0,0],[0,0,0],[0,0,0]]
>>> list_a = deepcopy(list_n)
>>> list_b = deepcopy(list_n)
>>> 
>>> list_a[0][0] = 1
>>> list_a
[[1, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> list_b
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
>>> 

¿Cuándo debería usar deepcopy()?

Uno de los mayores inconvenientes de usar deepcopy() es que se necesita una cantidad considerable de tiempo para "copiar en profundidad" listas anidadas superficialmente.

Si su lista solo está anidada superficialmente (de dos a tres niveles de profundidad), simplemente debe usar una comprensión de lista anidada en lugar de deepcopy(). Usar este método sería sustancialmente más eficiente (gracias a @jonrsharpe por señalar esto):

>>> list_n = [[0,0,0],[0,0,0],[0,0,0]]
>>> list_a = [x[:] for x in list_n]
>>> list_b = [x[:] for x in list_n]

La eficiencia obtenida con este método se puede observar con timeit módulo en la biblioteca estándar:

>>> import timeit
>>> 
>>> setup="from copy import deepcopy; list_n = [[0,0,0],[0,0,0],[0,0,0]]"
>>> timeit.timeit(setup=setup, stmt="deepcopy(list_n)")
24.223977088928223
>>> timeit.timeit(setup=setup, stmt="[x[:] for x in list_n]")
1.2281990051269531
>>>  

Sin embargo, si su lista es más profunda, debería optar por usar deepcopy() en su lugar, incluso si parece algo abultado. Por lo general, nunca es necesario sacrificar la legibilidad en lugar de la eficiencia. Además, a medida que la comprensión de la lista se vuelve cada vez más compleja, la eficiencia sobre deepcopy() comienza a disminuir.

¿Por qué deepcopy() es tan lento?

La razón por la que deepcopy() es mucho más lento que la mayoría de los otros métodos (gracias a @Felix por preguntar), es porque deepcopy() hace mucho más trabajo que la simple comprensión de una lista. a diferencia de la comprensión de la lista, deecopy() debe funcionar en una lista anidada arbitrariamente, posible con muchos niveles de anidación. Por lo tanto, es excesivo usarlo en una lista anidada superficialmente y resultará en un tiempo de ejecución mucho más lento.

Para tener una mejor idea de lo que deepcopy() hace detrás de escena, puede echar un vistazo al código fuente de la función, ya que es código abierto y disponible para el público para su visualización.

11
user2357112 supports Monica 14 feb. 2018 a las 04:51
3
Como es solo una capa profunda y homogénea, sospecho que [l[:] for l in list_n] sería más eficiente que deepcopy.
 – 
jonrsharpe
14 ene. 2017 a las 02:25
2
Las composiciones de listas anidadas se volverían feas rápidamente para estructuras más profundas, ¡es cierto! Sin embargo, parece dos órdenes de magnitud más rápido, así que pensé en mencionarlo.
 – 
jonrsharpe
14 ene. 2017 a las 02:29
3
¡No me importa en absoluto! De hecho, tenías toda la razón. Usando el módulo timeit, la sincronización deepcopy() tomó alrededor de 30.5 segundos, ¡mientras que su método tomó solo alrededor de 1 segundo! Gracias por la actualización.
 – 
Christian Dean
14 ene. 2017 a las 02:38
1
¿Por qué deepcopy es tan lento? En realidad, no hace más trabajo que la comprensión de la lista, ¿verdad?
 – 
Felix Dombek
14 ene. 2017 a las 08:02
1
Si pagas por algo que no usas, está mal diseñado. Deberían haber implementado esto en C. A esta velocidad, cPickle / json son incluso más rápidos que deepcopy ...
 – 
Felix Dombek
14 ene. 2017 a las 15:56