Lista de cambios de listas reflejados inesperadamente en sublistas

646

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 í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 solucionarlo?

Charles Anderson
fuente

Respuestas:

561

Cuando escribes [x]*3obtienes, esencialmente, la lista [x, x, x]. Es decir, una lista con 3 referencias a la misma x. Cuando modifica este single x, es visible mediante las tres referencias:

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 se reevaluará [1]*4cada 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 como lo hace la comprensión de la lista. Eso es porque el operador de multiplicación *opera en objetos, sin ver expresiones. Cuando usa *para multiplicar [[1] * 4]por 3, *solo ve que la lista de 1 elemento [[1] * 4]evalúa, no[[1] * 4 texto de 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 quiere copias, y en general, puede que ni siquiera haya una forma de copiar el elemento.

La unica opcion * 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 comprensión de la lista reevalúa la expresión del elemento en cada iteración. [[1] * 4 for n in range(3)]reevalúa [1] * 4cada vez por la misma razón [x**2 for x in range(3)]reevalúax**2 cada vez. Cada evaluación de [1] * 4genera una nueva lista, por lo que la comprensión de la lista hace lo que quería.

Por cierto, [1] * 4tampoco copia los elementos de [1], pero eso no importa, ya que los enteros son inmutables. No puedes hacer algo así 1.value = 2y convertir un 1 en un 2.

CAdaker
fuente
24
Me sorprende que ningún cuerpo señale eso, la respuesta aquí es engañosa. [x]*3almacenar 3 referencias como [x, x, x]solo es correcto cuando xes mutable. Esto no funciona, por ejemplo a=[4]*3, para después a[0]=5,a=[5,4,4].
Allanqunzi
42
Técnicamente, sigue siendo correcto. [4]*3es esencialmente equivalente a x = 4; [x, x, x]. Sin embargo, es cierto que esto nunca causará ningún problema, ya que 4es inmutable. Además, su otro ejemplo no es realmente un caso diferente. a = [x]*3; a[0] = 5no causará problemas incluso si xes mutable, ya que no está modificando x, solo modifica a. No describiría mi respuesta como engañosa o incorrecta: simplemente no puede dispararse en el pie si se trata de objetos inmutables.
CAdaker
19
@Allanqunzi te equivocas. Hacer x = 1000; lst = [x]*2; lst[0] is lst[1]-> True. Python no distingue entre objetos mutables e inmutables aquí en absoluto.
timgeb
130
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

Marcos y objetos

Tutor de Python en vivo Visualizar

nadrimajstor
fuente
Entonces, ¿por qué si escribimos matrix = [[x] * 2] no produce 2 elementos para el mismo objeto como el ejemplo que describe, parece ser el mismo concepto, ¿qué me estoy perdiendo?
Ahmed Mohamed
@AhmedMohamed De hecho, hace una lista con dos elementos del mismo objeto exacto al que se xrefiere. Si comete un objeto único a nivel mundial con x = object()y luego hacer matrix = [[x] * 2]estos se vienen como verdadera:matrix[0][0] is matrix[0][1]
nadrimajstor
@nadrimajstor, entonces, ¿por qué el cambio en la matriz [0] no afecta a la matriz [1] como en el ejemplo anterior con la matriz 2D?
Ahmed Mohamed
@AhmedMohamed Surprise viene cuando haces una "copia" de la secuencia mutable (en nuestro ejemplo es a list) así que si a row = [x] * 2que a matrix = [row] * 2donde ambas filas son exactamente el mismo objeto, y ahora cambia a una fila de matrix[0][0] = yrepente se refleja en la otra(matrix[0][0] is matrix[1][0]) == True
nadrimajstor
@AhmedMohamed Eche un vistazo a Ned Batchelder: hechos y mitos sobre los nombres y valores de Python, ya que podría ofrecer una mejor explicación. :)
nadrimajstor
52

En realidad, esto es exactamente lo que esperarías. Descompongamos lo que está sucediendo aquí:

Usted escribe

lst = [[1] * 4] * 3

Esto es equivalente a:

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

Esto significa que lstes una lista con 3 elementos que apuntan todos lst1. Esto significa que las dos líneas siguientes son equivalentes:

lst[0][0] = 5
lst1[0] = 5

Como lst[0]es nada más lst1.

Para obtener el comportamiento deseado, puede usar la comprensión de la lista:

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.

PierreBdR
fuente
Sólo una pequeña adición a la respuesta agradable aquí: es evidente que usted está tratando con un mismo objeto si lo hace id(lst[0][0])y id(lst[1][0])ni siquiera id(lst[0])yid(lst[1])
Sergiy Kolodyazhnyy
36
[[1] * 4] * 3

o incluso:

[[1, 1, 1, 1]] * 3

Crea una lista que hace referencia a las [1,1,1,1]3 veces internas , 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.

Blair Conrad
fuente
3
Puede usar el operador "es" para descubrir esto. ls [0] es ls [1] devuelve True.
mipadi
9

Junto con la respuesta aceptada que explicaba el problema correctamente, dentro de su comprensión de la lista, si está utilizando python-2.x, el uso 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 una forma mucho más pitónica 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.onesy np.zerosy / 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])
Kasramvd
fuente
6

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

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

En esta bes una lista que contiene un elemento que es una referencia a la lista a. La lista aes mutable.

La multiplicación de una lista por un número entero es equivalente a agregarse la lista 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 cahora contiene dos referencias a la lista aque es equivalente ac = b * 2 .

Las preguntas frecuentes de Python también contienen una explicación de este comportamiento: ¿Cómo creo una lista multidimensional?

Zbyněk Winkler
fuente
6

myList = [[1]*4] * 3crea 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 a objse reflejará en tres lugares, donde objse 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 utiliza principalmente para crear una lista de literales . Aunque 1es inmutable, obj =[1]*4seguirá creando una lista de 14 veces repetidas 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 objse convertirán en [1,42,1,1] no [42,42,42,42] como algunos pueden suponer. Esto también se puede verificar:

>>> 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
jerrymouse
fuente
2
No se trata de literales. obj[2] = 42 reemplaza la referencia en el índice 2, en lugar de mutar el objeto al que hace referencia ese índice, que es lo que myList[2][0] = ...hace ( myList[2]es una lista, y la asignación altera la referencia en el índice 0 en esa lista). Por supuesto, los números enteros no son mutables, pero un montón de tipos de objetos son . ¡Y tenga en cuenta que la [....]notación de visualización de la lista también es una forma de sintaxis literal! No confunda objetos compuestos (como listas) y escalares (como enteros) con objetos mutables o inmutables.
Martijn Pieters
5

En palabras simples, esto está sucediendo porque en Python todo funciona por referencia , así que cuando creas una lista de esa manera, básicamente terminas con tales problemas.

Para resolver su problema, puede hacer cualquiera de ellos: 1. Use la documentación de la matriz numpy para numpy.empty 2. Agregue la lista a medida que llega a una lista. 3. También puedes usar el diccionario si quieres

Neeraj Komuravalli
fuente
2

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

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

myList = [z] * 3

Luego de esto, ejecute el siguiente código para que todo quede más claro. Lo que hace el código es básicamente imprimir la ids de los objetos obtenidos, que

Devuelve la "identidad" 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á 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 paso a paso. Tienes xcuál es 1, y una sola lista de elementos que ycontiene x. Su primer paso es y * 4obtener 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 xobjeto inicial . El paso neto es bastante similar. Básicamente lo haces z * 3, que es [[x, x, x, x]] * 3y regresa [[x, x, x, x], [x, x, x, x], [x, x, x, x]], por la misma razón que para el primer paso.

bagrat
fuente
2

Supongo que todos explican lo que está sucediendo. 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]]
awulll
fuente
2

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é la modificación del primer elemento de la primera lista no modificó el segundo elemento de cada lista? Eso es porque [0] * 2realmente es una lista de dos números, y una referencia a 0 no puede modificarse.

Si desea crear copias clonadas, intente 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]]
Adil Abbasi
fuente
2

@spelchekr de la multiplicación de la lista de Python: [[...]] * 3 hace 3 listas que se reflejan entre sí cuando se modifican y tuve la misma pregunta sobre "¿Por qué solo el * 3 externo crea más referencias mientras que el interno no? ¿Por qué no es todo 1s?

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 *3también crea referencias, pero sus referencias son inmutables, algo así como [&0, &0, &0], cuando cambiar li[0], no puede cambiar ninguna referencia subyacente de const int 0, por lo que puede cambiar la dirección de referencia a la nueva &1;
  • while ma=[&li, &li, &li]y lies mutable, por lo que cuando llama ma[0][0]=1, ma [0] [0] es igual a &li[0], por lo que todas las &liinstancias cambiarán su primera dirección a &1.
ouxiaogu
fuente
1

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
anand tripathi
fuente
1
Tenga en cuenta que el cuarto paso se puede eliminar si realiza el segundo paso:a.insert(0,[5,1,1,1])
U10-Adelante