Objetivo: aplicar las técnicas de slicing de secuencias para obtener un duplicado de una lista.
En el artículo anterior, El sagrado misterio de la inmutabilidad, pudimos apreciar un fenómeno curioso a la hora de trabajar con listas.
Supongamos las siguientes asignaciones aparentemente inocentes:
>>> x = [1, 2]
>>> y = x
Tiene toda la pinta de que y es una copia de x, ¿no?.
>>> y
[1, 2]
Si ahora modificamos la lista x:
>>> x.append(3)
>>> x
[1, 2, 3]
¿Qué habrá sucedido con y?
>>> y
[1, 2, 3]
Atención: ¡los cambios en x también han ocurrido en y! Esto es Python.
Lo que está pasando es que x e y se corresponden con el mismo objeto en memoria. Esto no tendría la menor importancia desde el punto de vista del programador si el objeto en cuestión fuera inmutable, como un número, un string o una tupla, pues no pueden ser modificados in situ. En cambio, con las listas tenemos un serio problema, como hemos podido comprobar.
¿Cómo hacemos, entonces, si necesitamos duplicar una lista de modo que cada una pueda vivir su vida libre independientemente de la otra?
Las técnicas de slicing de secuencias, que ya hemos dejado caer con anterioridad, permiten la independencia de las listas clonadas.
Repasemos brevemente el concepto de slicing, o troceado de una secuencia.
>>> x = 'pradera'
>>> x[1:5]
'rade'
Con esa instrucción extraemos desde el elemento cón índice 1 (recuerda que, en las secuencias, el primer elemento tiene por índice cero) hasta, y sin incluir, el de índice 5; es decir, desde el 1 al 4.
Fíjate en el formato de la instrucción: entre corchetes y separando los límites mediante los dos puntos.
Podemos omitir uno de los índices:
>>> x[:5]
'prade'
que equivale a tomar todos los caracteres desde el principio hasta el de índice cuatro.
>>> x[3:]
'dera'
que es lo mismo que extraer desde el que tiene por índice 3 hasta el último de la secuencia.
O también podemos suprimir los dos, equivalente a extraer todo desde el principio hasta el fin:
>>> x[:]
'pradera'
¿Qué sentido tiene realizar algo así, te preguntarás?
La clave estriba en que siempre que hagamos un slicing de una lista, obtendremos un objeto distinto, aunque sea un slicing como el anterior, de principio a fin.
Obsérvalo:
>>> x = [1, 2]
>>> y = x[:]
>>> y
[1, 2]
Tras esta operación y contiene el resultado de la extracción completa de los elementos de x, pero ahora sí que se trata de un duplicado independiente. Comprobémoslo:
>>> id(x)
162280364
>>> id(y)
162280556
También podríamos haber empleado el operador is para verificar si dos variables apuntan al mismo objeto, lo que es más rápido que comprobar las identidades individuales:
>>> x is y
False
Hemos logrado separar a las listas siamesas. Una última comprobación:
>>> x.append(3)
>>> x
[1, 2, 3]
>>> y
[1, 2]
Ley de vida: x e y dejan de ser almas paralelas y cada una recorre ahora su propio camino hasta que, inevitablemente, llegue el día en que dejen de existir.
Javier Montero Gabarró
http://elclubdelautodidacta.es/wp/2012/09/python-como-copiar-una-lista/
El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)
El Club del Autodidacta
Consulta el índice completo de artículos relacionados con Python.
muchas gracias me ha servido mucho esta información!
Buen día podria indicarme la falla que existe en e ste codigo, busco que la matriz AA permanezca igual en cada iteracion sin embargo al modificar la matriz B(qu en teoria es una copia de AA) la matriz cambia.
AA=[[1, 2, 3], [4, 5, 6], [7, 2, 9]]
N=len(AA)
Matriz = [ [0]*N for N in range(N)]
for i in range (N):
B = AA.copy()
del B[i]
for j in range(N):
for r in range(len(B)):
print(i, j,r )
del B[r][j]
print(AA)
print(B)
(i,j,r)= 0 0 0
AA= [[1, 2, 3], [5, 6], [7, 2, 9]]
[[5, 6], [7, 2, 9]]
(i,j,r)=0 0 1
AA= [[1, 2, 3], [5, 6], [2, 9]]
B=[[5, 6], [2, 9]]
(i,j,r)=0 1 0
AA= [[1, 2, 3], [5], [2, 9]]
B=[[5], [2, 9]]
(i,j,r)=0 1 1
AA= [[1, 2, 3], [5], [2]]
B=[[5], [2]]
0 2 0
En la versión que yo tengo (3.3.3.) no funciona así. El objeto sigue siendo el mismo hasta que es modificado.
>>> x = ‘Burlington’
>>> y = x[:]
>>> id(x)
31136888
>>> id(y)
31136888
>>> x = x + ‘ Loan Management’
>>> x
‘Burlington Loan Management’
>>> y
‘Burlington’
>>> id(x)
36856456
>>> id(y)
31136888
>>>
Gracias por el curso, me está resultando de mucha utilidad!!
Ojo, no te confundas. No es una cuestión de la versión, sino de que tú estás haciendo un duplicado mediante slicing de un string, no de una lista. Su comportamiento es completamente diferente.
Me alegro de que los artículos te sean útiles. Muchas gracias por pasarte y saludos!
Muchas gracias por la información. Ahora ya se asignar listas sin modificar la original. Gracias!!!!!
¡Dale caña!
Javier, realmente me salvaste con tu clarísima explicación de la filosofía Python!!! Llevaba todo el día sin entender por qué al tratar de hacer un duplicado de una lista, éstas no seguían «caminos separados». ¡Muchísimas gracias!. Otra cosa que me llamó la atención hace un tiempo y que aún no sé la razón, es por qué razón cuando computo 0.01*35 me devuelve 0.35000000000000003.
Saludos y gracias!!!
Buenos días, Enrique:
Me alegro mucho de que el artículo te haya servido de ayuda. Si has programado en otros lenguajes antes, es importante tomarse el tiempo en entender las peculiaridades de Python respecto a estos.
Lo segundo no es un problema de Python: bienvenido a la aritmética en punto flotante.
0,01 no se almacena internamente como 0.01. Prueba a convertirlo a binario, no sale exacto, tiene infinitos términos. Lo único que puede tratar Python de hacer es almacenarlo con la máxima precisión posible.
Aunque tu veas esto:
>>> x = 0.01
>>> x
>>> 0.01
Lo que estás viendo no es más que un redondeo que te hace Python, pero no te muestra el valor real que tiene almacenado.
Eso sí, tan pronto realizas aritmética con ese valor se manifiesta su naturaleza imprecisa, como has observado. Puedes tú redondearla por tu cuenta, o puedes usar módulos como decimal.
Esé sera objeto de otro artículo… 😉
Saludos
me pasó que traté de copiar una lista de listas y me arrojaba el mismo problema, ya que las listas internas no quedan independientes.
a=[ [1,2], [3,4,5] ]
b=a[:]
id(a) != id(b)
pero
id(a[k]) = id(b[k]) , k=0,…,len(a)-1
Pero en base a tu idea, pude resolverlo aplicando un for sobre las listas interiores:
a=[ [1,2], [3,4,5] ]
b=[ k[:] for k in a]
con lo anterior, tenemos:
id(a) != id(b)
y además
id(a[k]) != id(b[k]) , k=0,…,len(a)-1
Saludos y gracias!!!
saludos, y porqué no manejarlas como objetos diferentes con el metodo copy() ?
lista = [1,2,3,4]
lista2 = lista.copy()
así no reventarse la cabeza si es el mismo o no objeto.
Qué tal:
Es otra manera de hacer lo mismo, salvo que un poco más larga de escribir.
Saludos
Excelente post, mejor aun la ultima respuesta , gracias por la informacion
Lamento informar de que en la version 3.6 ya no funciona este método.
Lo solucioné utilizando el deepcopy:
from copy import deepcopy
lista = [[1, 2], [3, 4]]
lista_copiada = deepcopy(lista)
print(id(lista))
print(id(lista_copiada))
Hola, muy valioso tu aporte, te lo agradezco 😀
Eres un genio, gracias!!