Pregunta

Necesitaba crear una lista de listas en Python, así que escribí lo siguiente:

myList = [[1] * 4] * 3

La lista tenía este aspecto:

[[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 evitarlo?

¿Fue útil?

Solución

Cuando escribe [x] * 3 , obtiene esencialmente la lista [x, x, x] . Es decir, una lista con 3 referencias al mismo x . Cuando luego modifica este único x 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 _ in range(3)]

que volverá a evaluar [1] * 4 cada vez en lugar de evaluarlo una vez y hacer 3 referencias a 1 lista.


Podría preguntarse por qué * no puede hacer que los objetos independientes sean como lo hace la comprensión de la lista. Esto se debe a que el operador de multiplicación * opera en objetos, sin ver expresiones. Cuando utiliza * para multiplicar [[1] * 4] por 3, * solo ve la lista de 1 elemento [[ 1] * 4] se 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 volver a evaluar [[1] * 4] , ni idea de que incluso quiera 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 para n en el rango (3)] vuelve a evaluar [1] * 4 cada vez por el mismo motivo [x ** 2 para x en el rango (3)] vuelve a evaluar x ** 2 cada vez. Cada evaluación de [1] * 4 genera una nueva lista, por lo que la lista de comprensión hace lo que usted quería.

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.

Otros consejos

size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

Marcos y objetos

Live Python Tutor Visualize

En realidad, esto es exactamente lo que cabría esperar. Vamos a descomponer lo que está pasando aquí:

Tu escribes

lst = [[1] * 4] * 3

Esto es equivalente a:

lst1 = [1]*4
lst = [lst1]*3

Esto significa que lst es una lista con 3 elementos que apuntan 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 usar la comprensión de lista:

lst = [ [1]*4 for n in xrange(3) ]

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 [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ás 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.

Junto con la respuesta aceptada que explicó el problema correctamente, dentro de su 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) _ 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 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]

P.S. Usando numpy, si solo desea crear una matriz de unos o ceros, puede usar np.ones y np.zeros y / o para otro número use 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])

En palabras sencillas, esto sucede porque en Python todo funciona por referencia , por lo que cuando creas una lista de la lista de esa manera básicamente terminas con estos problemas.

Para resolver tu problema puedes hacer una de ellas: 1. Use la matriz numpy documentación para numpy.empty 2. Agregue la lista cuando llegue a una lista. 3. También puedes usar el diccionario si quieres

Los contenedores de Python contienen referencias a otros objetos. Vea este ejemplo:

>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]

En este b es una lista que contiene un elemento que es una referencia para listar a . La lista a es mutable.

La multiplicación de una lista por un número entero es equivalente a agregarla a sí misma varias veces (consulte 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 crear 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 más . Esto es equivalente a 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 * es principalmente usado para crear una lista de literales . Ya que 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 [42,42,42,42? como algunos pueden suponer.

Permítanos reescribir su código de la siguiente manera:

x = 1
y = [x]
z = y * 4

myList = [z] * 3

Luego de tener esto, ejecute el siguiente código para aclarar todo. Lo que hace el código es básicamente imprimir el id s de los objetos obtenidos, que

  

Devuelva la & # 8220; identidad & # 8221; de un objeto

y nos ayudará a identificarlos y analizar lo que sucede:

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 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 vamos a ir paso a paso. Tiene x , que es 1 , y una lista de elementos únicos y que contiene x . Su primer paso es y * 4 que le proporcionará 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 objeto inicial x . 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 el mismo motivo que para el primer paso.

Supongo que todos explican lo que está sucediendo. Sugiero una forma de resolverlo:

myList = [[1 para i en el rango (4)] para j en el rango (3)]

myList[0][0] = 5

print myList

Y luego tienes:

[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]

Intentando 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]]

¿Se 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]]

Al usar la función de lista incorporada, puedes hacer esto de esta manera

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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top