tuplas - print lista python
Lista de listas de cambios reflejados en sublistas inesperadamente (12)
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]]
Entonces cambié uno de los valores más íntimos:
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 evitarlo?
Cuando escribes [x]*3
obtienes, esencialmente, la lista [x, x, x]
. Es decir, una lista con 3 referencias al mismo x
. Cuando luego modifica este x
único, es visible a través de las tres referencias a él.
Para solucionarlo, debe asegurarse de crear una nueva lista en cada posición. Una forma de hacerlo es
[[1]*4 for n in range(3)]
que reevaluará [1]*4
cada vez en lugar de evaluarlo una vez y hacer 3 referencias a 1 lista.
Podría preguntarse por qué *
no se pueden hacer objetos independientes de la forma en que lo hace la comprensión de lista. Esto se debe a que el operador de multiplicación *
opera en objetos, sin ver expresiones. Cuando usa *
para multiplicar [[1] * 4]
por 3, *
solo ve la lista de 1 elemento [[1] * 4]
evalúa, no el texto de expresión [[1] * 4
. *
no tiene idea de cómo hacer copias de ese elemento, ni idea de cómo reevaluar [[1] * 4]
, ni idea de que incluso desee copias, y en general, puede 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.
En contraste, 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 usted desea.
Por cierto, [1] * 4
tampoco copia los elementos de [1]
, pero eso no importa, ya que los enteros son inmutables. No puedes hacer algo como 1.value = 2
y convertir un 1 en un 2.
En palabras sencillas, esto sucede porque en Python todo funciona por referencia , de modo que cuando creas una lista de la lista, básicamente terminas con estos problemas.
Para resolver su problema, puede hacer uno de ellos: 1. Use la documentación de la matriz numpy para numpy.empty 2. Agregue la lista a medida que llegue a una lista. 3. También puedes usar el diccionario si quieres.
En realidad, esto es exactamente lo que cabría esperar. Vamos a descomponer 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, todos apuntando a lst1
. Esto significa que las dos líneas siguientes son equivalentes:
lst[0][0] = 5
lst1[0] = 5
Como lst[0]
no es más que lst1
.
Para obtener el comportamiento deseado, puede utilizar la lista de comprensión:
lst = [ [1]*4 for n in xrange(3) ]
En este caso, la expresión se reevalúa para cada n, lo que lleva a una lista diferente.
Junto con la respuesta aceptada que explicó el problema correctamente, dentro de la lista de comprensión, si está usando python-2.x, use xrange()
que devuelve un generador que es más eficiente ( range()
en python 3 hace el mismo trabajo) _
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 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]
PS Usando numpy, si solo deseas crear una matriz de unos o ceros puedes usar np.ones
y np.zeros
y / o para otro número usa 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
es una lista que contiene un elemento que es una referencia para enumerar a
. La lista a
es mutable.
La multiplicación de una lista por un entero es equivalente a agregar la lista a sí misma varias veces (ver operaciones de secuencia comunes ). 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 la lista 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?
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 que todo quede más claro. Lo que hace el código es básicamente imprimir los id
de los objetos obtenidos, que
Devuelve la "identidad" de un objeto
y nos ayudará a identificarlos y analizar qué pasa:
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ás la siguiente salida:
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 vamos a ir paso a paso. Tienes x
que es 1
, y una sola lista de elementos y
contiene x
. Su primer paso es y * 4
que le dará una nueva lista z
, que básicamente es [x, x, x, x]
, es decir, crea una nueva lista que tendrá 4 elementos, que son referencias al objeto x
inicial. El paso neto es bastante similar. Básicamente haces z * 3
, que es [[x, x, x, x]] * 3
y devuelve [[x, x, x, x], [x, x, x, x], [x, x, x, x]]
, por la misma razón que para el primer paso.
Supongo que todos explican 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]]
Tratando de explicarlo más descriptivamente,
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]]
¿Notó por qué no modificar el primer elemento de la primera lista no modificó el segundo elemento de cada lista? Esto se debe a que [0] * 2
realmente es una lista de dos números, y una referencia a 0 no se puede modificar.
Si desea crear copias clónicas, intente con 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]]
Usando la función de lista incorporada puedes 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
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
obj = [1,1,1,1]; myList = [obj]*3
. Cualquier modificación de obj
se reflejará en tres lugares, donde se haga referencia a obj
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 utiliza principalmente para crear una lista de literales . Como 1
es un literal, por lo tanto obj =[1]*4
creará [1,1,1,1]
donde cada 1
es atómico y no una referencia de 1
repetida 4 veces. Esto significa que si hacemos obj[2]=42
, entonces obj
se convertirá en [1,1,42,1]
no en como algunos pueden suponer. [42,42,42,42]
[[1] * 4] * 3
o incluso:
[[1, 1, 1, 1]] * 3
Crea una lista que hace referencia al [1,1,1,1]
interno 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 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.
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]