Lista de cambios de listas reflejados inesperadamente en sublistas
Necesitaba crear una lista de listas en Python, así que escribí lo siguiente:
myList = [[1] * 4] * 3
La lista se veía así:
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
Luego cambié uno de los valores más internos:
myList[0][0] = 5
Ahora mi lista se ve así:
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]
que no es lo que quería o esperaba. ¿Alguien puede explicar qué está pasando y cómo solucionarlo?
Respuestas
Cuando escribes [x]*3
obtienes, esencialmente, la lista [x, x, x]
. Es decir, una lista con 3 referencias al mismo x
. Cuando luego modificas este single x
, es visible a través de las tres referencias a él:
x = [1] * 4
l = [x] * 3
print(f"id(x): {id(x)}")
# id(x): 140560897920048
print(
f"id(l[0]): {id(l[0])}\n"
f"id(l[1]): {id(l[1])}\n"
f"id(l[2]): {id(l[2])}"
)
# id(l[0]): 140560897920048
# id(l[1]): 140560897920048
# id(l[2]): 140560897920048
x[0] = 42
print(f"x: {x}")
# x: [42, 1, 1, 1]
print(f"l: {l}")
# l: [[42, 1, 1, 1], [42, 1, 1, 1], [42, 1, 1, 1]]
Para solucionarlo, debe asegurarse de crear una nueva lista en cada posición. Una forma de hacerlo es
[[1]*4 for _ in range(3)]
que reevaluará [1]*4
cada vez en lugar de evaluarlo una vez y hacer 3 referencias a 1 lista.
Quizás se pregunte por qué *
no puede hacer objetos independientes de la forma en que lo hace la lista de comprensión. Eso es porque el operador de multiplicación *
opera sobre objetos, sin ver expresiones. Cuando usa *
para multiplicar [[1] * 4]
por 3, *
solo ve la lista de 1 elemento [[1] * 4]
evaluado, no el [[1] * 4
texto de la expresión. *
no tiene idea de cómo hacer copias de ese elemento, no tiene idea de cómo reevaluar [[1] * 4]
, y ni siquiera tiene idea de si quiere copias y, en general, es posible que ni siquiera haya una forma de copiar el elemento.
La única opción que *
tiene es hacer nuevas referencias a la sublista existente en lugar de intentar hacer nuevas sublistas. Cualquier otra cosa sería inconsistente o requeriría un rediseño importante de las decisiones fundamentales de diseño del lenguaje.
Por el contrario, una lista de comprensión reevalúa la expresión del elemento en cada iteración. [[1] * 4 for n in range(3)]
reevalúa [1] * 4
cada vez por la misma razón [x**2 for x in range(3)]
reevalúa x**2
cada vez. Cada evaluación de [1] * 4
genera una nueva lista, por lo que la comprensión de la lista hace lo que desea.
Por cierto, [1] * 4
tampoco copia los elementos de [1]
, pero eso no importa, ya que los números enteros son inmutables. No puedes hacer algo como 1.value = 2
convertir un 1 en un 2.
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]
Tutor de Python en vivo Visualizar
En realidad, esto es exactamente lo que cabría esperar. Descompongamos lo que está pasando aquí:
Usted escribe
lst = [[1] * 4] * 3
Esto es equivalente a:
lst1 = [1]*4
lst = [lst1]*3
Esto significa que lst
es una lista con 3 elementos a los que apuntan lst1
. Esto significa que las dos siguientes líneas son equivalentes:
lst[0][0] = 5
lst1[0] = 5
Como lst[0]
es nada más lst1
.
Para obtener el comportamiento deseado, puede utilizar la comprensión de listas:
lst = [ [1]*4 for n in range(3) ] #python 3
lst = [ [1]*4 for n in xrange(3) ] #python 2
En este caso, la expresión se vuelve a evaluar para cada n, lo que lleva a una lista diferente.
[[1] * 4] * 3
o incluso:
[[1, 1, 1, 1]] * 3
Crea una lista que hace referencia al interno [1,1,1,1]
3 veces, no tres copias de la lista interna, por lo que cada vez que modifique la lista (en cualquier posición), verá el cambio tres veces.
Es lo mismo que en este ejemplo:
>>> inner = [1,1,1,1]
>>> outer = [inner]*3
>>> outer
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
>>> inner[0] = 5
>>> outer
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]
donde probablemente sea un poco menos sorprendente.
Junto con la respuesta aceptada que explicó el problema correctamente, dentro de la comprensión de su lista, si está usando python-2.x, use xrange()
que devuelva un generador que es más eficiente ( range()
en Python 3 hace el mismo trabajo) en _
lugar de la variable desechable n
:
[[1]*4 for _ in xrange(3)] # and in python3 [[1]*4 for _ in range(3)]
Además, como una forma mucho más Pythonic que puede usar itertools.repeat()para crear un objeto iterador de elementos repetidos:
>>> a=list(repeat(1,4))
[1, 1, 1, 1]
>>> a[0]=5
>>> a
[5, 1, 1, 1]
EP Utilizar numpy, si sólo desea crear una serie de unos o ceros se pueden utilizar np.ones
y np.zeros
y / o para otro uso número np.repeat()
:
In [1]: import numpy as np
In [2]:
In [2]: np.ones(4)
Out[2]: array([ 1., 1., 1., 1.])
In [3]: np.ones((4, 2))
Out[3]:
array([[ 1., 1.],
[ 1., 1.],
[ 1., 1.],
[ 1., 1.]])
In [4]: np.zeros((4, 2))
Out[4]:
array([[ 0., 0.],
[ 0., 0.],
[ 0., 0.],
[ 0., 0.]])
In [5]: np.repeat([7], 10)
Out[5]: array([7, 7, 7, 7, 7, 7, 7, 7, 7, 7])
Los contenedores de Python contienen referencias a otros objetos. Vea este ejemplo:
>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]
En esta b
hay una lista que contiene un elemento que es una referencia a la lista a
. La lista a
es mutable.
La multiplicación de una lista por un número entero equivale a sumar la lista a sí misma varias veces (consulte las operaciones comunes de secuencia ). Entonces continuando con el ejemplo:
>>> c = b + b
>>> c
[[1], [1]]
>>>
>>> a[0] = 2
>>> c
[[2], [2]]
Podemos ver que la lista c
ahora contiene dos referencias a list a
que es equivalente a c = b * 2
.
Las preguntas frecuentes de Python también contienen una explicación de este comportamiento: ¿Cómo creo una lista multidimensional?
myList = [[1]*4] * 3
crea un objeto de lista [1,1,1,1]
en la memoria y copia su referencia 3 veces. Esto es equivalente a obj = [1,1,1,1]; myList = [obj]*3
. Cualquier modificación de obj
se reflejará en tres lugares, donde sea que obj
se haga referencia en la lista. La declaración correcta sería:
myList = [[1]*4 for _ in range(3)]
o
myList = [[1 for __ in range(4)] for _ in range(3)]
Lo importante a tener en cuenta aquí es que el *
operador se usa principalmente para crear una lista de literales . Aunque 1
es inmutable, obj =[1]*4
seguirá creando una lista de 1
repetidos 4 veces para formar [1,1,1,1]
. Pero si se hace alguna referencia a un objeto inmutable, el objeto se sobrescribe con uno nuevo.
Esto significa que si lo hacemos obj[1]=42
, entonces obj
se convertirá en [1,42,1,1]
no
como algunos pueden suponer. Esto también se puede verificar:
[42,42,42,42]
>>> myList = [1]*4
>>> myList
[1, 1, 1, 1]
>>> id(myList[0])
4522139440
>>> id(myList[1]) # Same as myList[0]
4522139440
>>> myList[1] = 42 # Since myList[1] is immutable, this operation overwrites myList[1] with a new object changing its id.
>>> myList
[1, 42, 1, 1]
>>> id(myList[0])
4522139440
>>> id(myList[1]) # id changed
4522140752
>>> id(myList[2]) # id still same as myList[0], still referring to value `1`.
4522139440
Permítanos reescribir su código de la siguiente manera:
x = 1
y = [x]
z = y * 4
myList = [z] * 3
Luego, teniendo esto, ejecute el siguiente código para dejar todo más claro. Lo que hace el código es básicamente imprimir los ids de los objetos obtenidos, que
Devuelve la "identidad" de un objeto
y nos ayudará a identificarlos y analizar lo que ocurre:
print("myList:")
for i, subList in enumerate(myList):
print("\t[{}]: {}".format(i, id(subList)))
for j, elem in enumerate(subList):
print("\t\t[{}]: {}".format(j, id(elem)))
Y obtendrá el siguiente resultado:
x: 1
y: [1]
z: [1, 1, 1, 1]
myList:
[0]: 4300763792
[0]: 4298171528
[1]: 4298171528
[2]: 4298171528
[3]: 4298171528
[1]: 4300763792
[0]: 4298171528
[1]: 4298171528
[2]: 4298171528
[3]: 4298171528
[2]: 4300763792
[0]: 4298171528
[1]: 4298171528
[2]: 4298171528
[3]: 4298171528
Así que ahora vayamos paso a paso. Tienes x
cuál es 1
, y una lista de elementos que y
contiene x
. Tu primer paso es y * 4
que te dará una nueva lista z
, que es básicamente [x, x, x, x]
, es decir, crea una nueva lista que tendrá 4 elementos, que son referencias al x
objeto inicial . El paso neto es bastante similar. Básicamente lo haces z * 3
, que es [[x, x, x, x]] * 3
y vuelve [[x, x, x, x], [x, x, x, x], [x, x, x, x]]
, por la misma razón que en el primer paso.
En palabras simples, esto está sucediendo porque en Python todo funciona por referencia , por lo que cuando crea una lista de lista de esa manera, básicamente termina con tales problemas.
Para resolver su problema, puede hacer cualquiera de ellos: 1. Use la documentación de matriz de numpy para numpy.empty 2. Agregue la lista a medida que llega a una lista. 3. También puede utilizar el diccionario si lo desea
Supongo que todo el mundo explica lo que está pasando. Sugiero una forma de resolverlo:
myList = [[1 for i in range(4)] for j in range(3)]
myList[0][0] = 5
print myList
Y luego tienes:
[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
@spelchekr de la multiplicación de listas de Python: [[...]] * 3 hace 3 listas que se reflejan entre sí cuando se modifican y tenía la misma pregunta sobre "¿Por qué solo el * 3 externo crea más referencias mientras que el interno no ? ¿Por qué no son todos 1? "
li = [0] * 3
print([id(v) for v in li]) # [140724141863728, 140724141863728, 140724141863728]
li[0] = 1
print([id(v) for v in li]) # [140724141863760, 140724141863728, 140724141863728]
print(id(0)) # 140724141863728
print(id(1)) # 140724141863760
print(li) # [1, 0, 0]
ma = [[0]*3] * 3 # mainly discuss inner & outer *3 here
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
ma[0][0] = 1
print([id(li) for li in ma]) # [1987013355080, 1987013355080, 1987013355080]
print(ma) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
Aquí está mi explicación después de probar el código anterior:
- El interno
*3
también crea referencias, pero sus referencias son inmutables, algo así como[&0, &0, &0]
, cuando cambiarli[0]
, no puede cambiar ninguna referencia subyacente de const int0
, por lo que puede simplemente cambiar la dirección de referencia a la nueva&1
; - while
ma=[&li, &li, &li]
yli
es mutable, por lo que cuando llamasma[0][0]=1
, ma [0] [0] es igual a&li[0]
, por lo que todas las&li
instancias cambiarán su primera dirección a&1
.
Tratando de explicarlo de manera más descriptiva,
Operación 1:
x = [[0, 0], [0, 0]]
print(type(x)) # <class 'list'>
print(x) # [[0, 0], [0, 0]]
x[0][0] = 1
print(x) # [[1, 0], [0, 0]]
Operación 2:
y = [[0] * 2] * 2
print(type(y)) # <class 'list'>
print(y) # [[0, 0], [0, 0]]
y[0][0] = 1
print(y) # [[1, 0], [1, 0]]
¿Se dio cuenta de por qué la modificación del primer elemento de la primera lista no modificó el segundo elemento de cada lista? Eso es porque [0] * 2
realmente es una lista de dos números y una referencia a 0 no se puede modificar.
Si desea crear copias clonadas, pruebe la Operación 3:
import copy
y = [0] * 2
print(y) # [0, 0]
y = [y, copy.deepcopy(y)]
print(y) # [[0, 0], [0, 0]]
y[0][0] = 1
print(y) # [[1, 0], [0, 0]]
otra forma interesante de crear copias clonadas, Operación 4:
import copy
y = [0] * 2
print(y) # [0, 0]
y = [copy.deepcopy(y) for num in range(1,5)]
print(y) # [[0, 0], [0, 0], [0, 0], [0, 0]]
y[0][0] = 5
print(y) # [[5, 0], [0, 0], [0, 0], [0, 0]]
Al usar la función de lista incorporada, puede hacer esto
a
out:[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
#Displaying the list
a.remove(a[0])
out:[[1, 1, 1, 1], [1, 1, 1, 1]]
# Removed the first element of the list in which you want altered number
a.append([5,1,1,1])
out:[[1, 1, 1, 1], [1, 1, 1, 1], [5, 1, 1, 1]]
# append the element in the list but the appended element as you can see is appended in last but you want that in starting
a.reverse()
out:[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
#So at last reverse the whole list to get the desired list
Estas preguntas tienen muchas respuestas, estoy agregando mi respuesta para explicar las mismas en forma de diagrama.
La forma en que creó el 2D, crea una lista poco profunda
arr = [[0]*cols]*row
En cambio, si desea actualizar los elementos de la lista, debe usar
rows, cols = (5, 5)
arr = [[0 for i in range(cols)] for j in range(rows)]
Explicación :
Uno puede crear una lista usando:
arr = [0]*N
o
arr = [0 for i in range(N)]
En el primer caso, todos los índices de la matriz apuntan al mismo objeto entero
y cuando asigna un valor a un índice en particular, se crea un nuevo objeto int, por ejemplo, arr[4] = 5
crea
Ahora veamos qué sucede cuando creamos una lista de lista, en este caso, todos los elementos de nuestra lista superior apuntarán a la misma lista
Y si actualiza el valor de cualquier índice, se creará un nuevo objeto int. Pero dado que todos los índices de lista de nivel superior apuntan a la misma lista, todas las filas se verán iguales. Y tendrá la sensación de que actualizar un elemento es actualizar todos los elementos de esa columna.
Créditos: Gracias a Pranav Devarakonda por la fácil explicación aquí.
Llegué aquí porque estaba buscando cómo podía anidar un número arbitrario de listas. Hay muchas explicaciones y ejemplos específicos arriba, pero puede generalizar N lista dimensional de listas de listas de ... con la siguiente función recursiva:
import copy
def list_ndim(dim, el=None, init=None):
if init is None:
init = el
if len(dim)> 1:
return list_ndim(dim[0:-1], None, [copy.copy(init) for x in range(dim[-1])])
return [copy.deepcopy(init) for x in range(dim[0])]
Realiza su primera llamada a la función de esta manera:
dim = (3,5,2)
el = 1.0
l = list_ndim(dim, el)
donde (3,5,2)
es una tupla de las dimensiones de la estructura (similar al shape
argumento numpy ), y 1.0
es el elemento con el que desea que se inicialice la estructura (también funciona con None). Tenga en cuenta que el init
argumento solo lo proporciona la llamada recursiva para transferir las listas de niños anidadas
salida de arriba:
[[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]],
[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]],
[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]]]
establecer elementos específicos:
l[1][3][1] = 56
l[2][2][0] = 36.0+0.0j
l[0][1][0] = 'abc'
salida resultante:
[[[1.0, 1.0], ['abc', 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]],
[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 56.0], [1.0, 1.0]],
[[1.0, 1.0], [1.0, 1.0], [(36+0j), 1.0], [1.0, 1.0], [1.0, 1.0]]]
la naturaleza no mecanografiada de las listas se demuestra arriba
Tenga en cuenta que los elementos de la secuencia no se copian; se hace referencia a ellos varias veces . Esto a menudo persigue a los nuevos programadores de Python; considerar:
>>> lists = [[]] * 3
>>> lists
[[], [], []]
>>> lists[0].append(3)
>>> lists
[[3], [3], [3]]
Lo que ha sucedido es que [[]]
es una lista de un elemento que contiene una lista vacía, por lo que los tres elementos de [[]] * 3
son referencias a esta única lista vacía. La modificación de cualquiera de los elementos de las listas modifica esta única lista.
Otro ejemplo para explicar esto es el uso de matrices multidimensionales .
Probablemente intentó hacer una matriz multidimensional como esta:
>>> A = [[**None**] * 2] * 3
Esto parece correcto si lo imprime:
>>> A
[[None, None], [None, None], [None, None]]
Pero cuando asigna un valor, aparece en varios lugares:
>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]
La razón es que replicar una lista con *
no crea copias, solo crea referencias a los objetos existentes. El 3 crea una lista que contiene 3 referencias a la misma lista de longitud dos. Los cambios en una fila se mostrarán en todas las filas, lo que es casi seguro que no es lo que desea.