Lista de cambios de listas reflejados inesperadamente en sublistas

Oct 27 2008

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

614 CAdaker Oct 27 2008 at 22:03

Cuando escribes [x]*3obtienes, 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]*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 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] * 4texto 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] * 4cada vez por la misma razón [x**2 for x in range(3)]reevalúa x**2cada vez. Cada evaluación de [1] * 4genera una nueva lista, por lo que la comprensión de la lista hace lo que desea.

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

140 nadrimajstor Aug 27 2013 at 06:17
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

Tutor de Python en vivo Visualizar

56 PierreBdR Oct 27 2008 at 22:07

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 lstes 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.

38 BlairConrad Oct 27 2008 at 22:02
[[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.

10 Kasravnd Jun 18 2015 at 00:08

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.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])
7 ZbyněkWinkler Apr 06 2016 at 20:40

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

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

En esta bhay 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 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 cahora contiene dos referencias a list aque 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?

7 jerrymouse Apr 06 2017 at 12:36

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 de objse reflejará en tres lugares, donde sea que 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 usa principalmente para crear una lista de literales . Aunque 1es inmutable, obj =[1]*4seguirá creando una lista de 1repetidos 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 objse 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
5 bagrat Jun 10 2015 at 21:38

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 xcuál es 1, y una lista de elementos que ycontiene x. Tu primer paso es y * 4que 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 xobjeto inicial . El paso neto es bastante similar. Básicamente lo haces z * 3, que es [[x, x, x, x]] * 3y vuelve [[x, x, x, x], [x, x, x, x], [x, x, x, x]], por la misma razón que en el primer paso.

5 NeerajKomuravalli Jun 14 2016 at 13:36

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

4 awulll Apr 24 2016 at 20:31

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]]
4 ouxiaogu Aug 09 2019 at 15:37

@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 *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 simplemente cambiar la dirección de referencia a la nueva &1;
  • while ma=[&li, &li, &li]y lies mutable, por lo que cuando llamas 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.
3 AdilAbbasi Aug 10 2016 at 14:09

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] * 2realmente 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]]
2 AnandTripathi Jul 15 2016 at 20:48

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
2 DeepakPatankar Jun 21 2020 at 18:34

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] = 5crea

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í.

Brian Oct 23 2020 at 02:57

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 shapeargumento numpy ), y 1.0es el elemento con el que desea que se inicialice la estructura (también funciona con None). Tenga en cuenta que el initargumento 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

mishsx Nov 23 2020 at 02:45

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 [[]] * 3son 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.