Elenco delle modifiche alle liste riflesse in modo imprevisto nelle liste secondarie


646

Avevo bisogno di creare un elenco di elenchi in Python, quindi ho digitato quanto segue:

myList = [[1] * 4] * 3

L'elenco era simile al seguente:

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

Quindi ho modificato uno dei valori più interni:

myList[0][0] = 5

Ora il mio elenco è simile al seguente:

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

che non è quello che volevo o mi aspettavo. Qualcuno può spiegare cosa sta succedendo e come aggirarlo?

Risposte:


561

Quando scrivi [x]*3ottieni essenzialmente l'elenco [x, x, x]. Cioè, un elenco con 3 riferimenti allo stesso x. Quando poi modifichi questo singolo x, è visibile attraverso tutti e tre i riferimenti ad esso:

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

Per risolverlo, devi assicurarti di creare un nuovo elenco in ogni posizione. Un modo per farlo è

[[1]*4 for _ in range(3)]

che rivaluterà [1]*4ogni volta invece di valutarlo una volta e fare 3 riferimenti a 1 elenco.


Potresti chiederti perché *non è possibile creare oggetti indipendenti come fa la comprensione dell'elenco. Questo perché l'operatore di moltiplicazione *opera su oggetti, senza vedere le espressioni. Quando si utilizza *per moltiplicare [[1] * 4]per 3, viene visualizzato *solo l'elenco a 1 elemento [[1] * 4], non il [[1] * 4testo dell'espressione. *non ha idea di come fare copie di quell'elemento, non ha idea di come rivalutare [[1] * 4], e non ha nemmeno idea di volerne copie, e in generale, potrebbe non esserci nemmeno un modo per copiare l'elemento.

L'unica opzione *è quella di fare nuovi riferimenti all'elenco secondario esistente invece di provare a creare nuovi elenchi. Qualsiasi altra cosa sarebbe incoerente o richiederebbe una riprogettazione importante delle decisioni fondamentali sulla progettazione del linguaggio.

Al contrario, una comprensione dell'elenco rivaluta l'espressione dell'elemento su ogni iterazione. [[1] * 4 for n in range(3)]rivaluta [1] * 4ogni volta per lo stesso motivo [x**2 for x in range(3)]rivaluta x**2ogni volta. Ogni valutazione di [1] * 4genera un nuovo elenco, quindi la comprensione dell'elenco fa quello che volevi.

Per inciso, [1] * 4inoltre, non copia gli elementi di [1], ma non importa, poiché i numeri interi sono immutabili. Non puoi fare qualcosa del genere 1.value = 2e trasformare un 1 in un 2.


24
Sono sorpreso che nessun organismo lo dica, la risposta qui è fuorviante. [x]*3memorizzare 3 riferimenti come [x, x, x]è giusto solo quando xè mutabile. Questo non funziona per esempio a=[4]*3, dove dopo a[0]=5,a=[5,4,4].
Allanqunzi

42
Tecnicamente, è ancora corretto. [4]*3è essenzialmente equivalente a x = 4; [x, x, x]. È vero, tuttavia, che ciò non causerà mai alcun problema poiché 4è immutabile. Inoltre, l'altro esempio non è un caso diverso. a = [x]*3; a[0] = 5non causerà problemi anche se xè mutevole, poiché non si sta modificando x, ma solo modificando a. Non descriverei la mia risposta come fuorviante o errata - non puoi spararti ai piedi se hai a che fare con oggetti immutabili.
CAdaker,

19
@Allanqunzi ti sbagli. Fare x = 1000; lst = [x]*2; lst[0] is lst[1]-> True. Python qui non distingue tra oggetti mutabili e oggetti immutabili.
timgeb,

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

Cornici e oggetti

Tutor Live Python Visualizza


Quindi, perché se scriviamo matrix = [[[]] * 2] non fa 2 elemnts per lo stesso oggetto come nell'esempio che descrivi, sembra essere lo stesso concetto, cosa mi sto perdendo?
Ahmed Mohamed,

@AhmedMohamed Effettivamente crea un elenco con due elementi dello stesso oggetto esatto a cui si xriferisce. Se crei un oggetto unico a livello globale con x = object()e poi realizzi che matrix = [[x] * 2]questi diventano realtà:matrix[0][0] is matrix[0][1]
nadrimajstor,

@nadrimajstor, quindi perché il cambiamento nella matrice [0] non influenza la matrice [1] come nell'esempio sopra con la matrice 2d.
Ahmed Mohamed,

@AhmedMohamed La sorpresa arriva quando fai una "copia" di una sequenza mutabile (nel nostro esempio è a list) quindi se a row = [x] * 2che una matrix = [row] * 2dove entrambe le righe sono esattamente lo stesso oggetto, e ora le modifiche a una riga matrix[0][0] = ysi riflettono improvvisamente nell'altra(matrix[0][0] is matrix[1][0]) == True
nadrimajstor

@AhmedMohamed Dai un'occhiata a Ned Batchelder - Fatti e miti sui nomi e valori di Python in quanto potrebbe offrire una spiegazione migliore. :)
nadrimajstor,

52

In realtà, questo è esattamente quello che ti aspetteresti. Decomponiamo ciò che sta accadendo qui:

Scrivi

lst = [[1] * 4] * 3

Ciò equivale a:

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

Questo significa che lstè un elenco con 3 elementi che puntano tutti lst1. Ciò significa che le due righe seguenti sono equivalenti:

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

Come lst[0]non è altro che lst1.

Per ottenere il comportamento desiderato, è possibile utilizzare la comprensione dell'elenco:

lst = [ [1]*4 for n in range(3) ] #python 3
lst = [ [1]*4 for n in xrange(3) ] #python 2

In questo caso, l'espressione viene rivalutata per ogni n, portando a un elenco diverso.


Solo una piccola aggiunta alla bella risposta qui: è evidente che hai a che fare con lo stesso oggetto se lo fai id(lst[0][0])e id(lst[1][0])o anche id(lst[0])eid(lst[1])
Sergiy Kolodyazhnyy,

36
[[1] * 4] * 3

o anche:

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

Crea un elenco che fa riferimento alle [1,1,1,1]3 volte interne , non a tre copie della lista interna, quindi ogni volta che modifichi l'elenco (in qualsiasi posizione), vedrai la modifica tre volte.

È lo stesso di questo esempio:

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

dove probabilmente è un po 'meno sorprendente.


3
È possibile utilizzare l'operatore "is" per scoprirlo. ls [0] is ls [1] restituisce True.
mipadi,

9

Accanto alla risposta accettata che ha spiegato correttamente il problema, all'interno della comprensione della tua lista, se stai usando l'uso di python-2.x xrange()che restituisce un generatore che è più efficiente ( range()in python 3 fa lo stesso lavoro) _invece della variabile usa e getta n:

[[1]*4 for _ in xrange(3)]      # and in python3 [[1]*4 for _ in range(3)]

Inoltre, come un modo molto più Pythonic puoi usare itertools.repeat()per creare un oggetto iteratore di elementi ripetuti:

>>> a=list(repeat(1,4))
[1, 1, 1, 1]
>>> a[0]=5
>>> a
[5, 1, 1, 1]

PS Usando numpy, se vuoi solo creare una matrice di uno o zero puoi usare np.onese np.zerose / o per altri numeri usare 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])

6

I contenitori Python contengono riferimenti ad altri oggetti. Vedi questo esempio:

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

In questo bè un elenco che contiene un elemento che è un riferimento all'elenco a. L'elenco aè modificabile.

La moltiplicazione di un elenco per un numero intero equivale ad aggiungere l'elenco a se stesso più volte (vedere operazioni di sequenza comuni ). Quindi continuando con l'esempio:

>>> c = b + b
>>> c
[[1], [1]]
>>>
>>> a[0] = 2
>>> c
[[2], [2]]

Possiamo vedere che l'elenco cora contiene due riferimenti all'elenco a acui è equivalente c = b * 2.

Le FAQ di Python contengono anche una spiegazione di questo comportamento: come posso creare un elenco multidimensionale?


6

myList = [[1]*4] * 3crea un oggetto elenco [1,1,1,1]in memoria e ne copia il riferimento 3 volte. Questo equivale a obj = [1,1,1,1]; myList = [obj]*3. Ogni modifica a objverrà riflessa in tre punti, ovunque objsia indicato nell'elenco. La frase giusta sarebbe:

myList = [[1]*4 for _ in range(3)]

o

myList = [[1 for __ in range(4)] for _ in range(3)]

La cosa importante da notare qui è che l' *operatore viene utilizzato principalmente per creare un elenco di letterali . Sebbene 1sia immutabile, obj =[1]*4creerà comunque un elenco di 14 volte ripetute sopra al modulo [1,1,1,1]. Ma se viene fatto un riferimento a un oggetto immutabile, l'oggetto viene sovrascritto con uno nuovo.

Ciò significa che se lo facciamo obj[1]=42, allora nonobj diventerà come alcuni potrebbero supporre. Questo può anche essere verificato:[1,42,1,1] [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

2
Non si tratta di letterali. obj[2] = 42 sostituisce il riferimento all'indice 2, al contrario della mutazione dell'oggetto a cui fa riferimento quell'indice, che è ciò che myList[2][0] = ...fa ( myList[2]è un elenco e l'assegnazione modifica il riferimento all'indice 0 nell'elenco). Naturalmente, gli interi non sono mutabili, ma un sacco di tipi di oggetto sono . E nota che la [....]notazione di visualizzazione dell'elenco è anche una forma di sintassi letterale! Non confondere oggetti composti (come elenchi) e scalari (come numeri interi), con oggetti mutabili vs. immutabili.
Martijn Pieters

5

In parole semplici questo accade perché in Python tutto funziona come riferimento , quindi quando si crea un elenco di elenchi in quel modo si finisce sostanzialmente con tali problemi.

Per risolvere il problema è possibile eseguire una di queste operazioni: 1. Utilizzare la documentazione dell'array numpy per numpy.empty 2. Aggiungere l'elenco man mano che si arriva a un elenco. 3. Puoi anche usare il dizionario, se lo desideri


2

Riscriviamo il codice nel modo seguente:

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

myList = [z] * 3

Quindi, esegui questo codice per rendere tutto più chiaro. Ciò che il codice fa è fondamentalmente stampare la ids degli oggetti ottenuti, che

Restituisce l '"identità" di un oggetto

e ci aiuterà a identificarli e ad analizzare cosa succede:

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)))

E otterrai il seguente output:

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

Quindi ora andiamo passo per passo. Hai xquale è 1, e un singolo elenco di elementi ycontenente x. Il tuo primo passo è quello y * 4che ti porterà un nuovo elenco z, che è fondamentalmente [x, x, x, x], cioè crea un nuovo elenco che avrà 4 elementi, che sono riferimenti xall'oggetto iniziale . Il passo netto è abbastanza simile. Fondamentalmente lo fai z * 3, che è [[x, x, x, x]] * 3e ritorna [[x, x, x, x], [x, x, x, x], [x, x, x, x]], per lo stesso motivo del primo passo.


2

Immagino che tutti spieghino cosa sta succedendo. Suggerisco un modo per risolverlo:

myList = [[1 for i in range(4)] for j in range(3)]

myList[0][0] = 5

print myList

E poi hai:

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

2

Cercando di spiegarlo in modo più descrittivo,

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

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

Notato perché la modifica del primo elemento del primo elenco non ha modificato il secondo elemento di ciascun elenco? Questo perché in [0] * 2realtà è un elenco di due numeri e un riferimento a 0 non può essere modificato.

Se si desidera creare copie clonate, provare l'operazione 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]]

un altro modo interessante per creare copie clonate, Operazione 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

@spelchekr dalla moltiplicazione dell'elenco Python: [[...]] * 3 crea 3 elenchi che si rispecchiano a vicenda quando modificati e ho avuto la stessa domanda su "Perché solo l'esterno * 3 crea più riferimenti mentre quello interno no ? Perché non è tutto 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]]

Ecco la mia spiegazione dopo aver provato il codice sopra:

  • Anche l'interno *3crea riferimenti, ma i suoi riferimenti sono immutabili, qualcosa del genere [&0, &0, &0], quindi quando cambiare li[0], non puoi cambiare alcun riferimento sottostante di const int 0, quindi puoi semplicemente cambiare l'indirizzo di riferimento in quello nuovo &1;
  • mentre ma=[&li, &li, &li]ed liè mutabile, quindi quando chiami ma[0][0]=1, ma [0] [0] è uguale a &li[0], quindi tutte le &liistanze cambieranno il suo primo indirizzo in &1.

1

Usando la funzione dell'elenco integrato puoi fare così

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

1
Si noti che il quarto passaggio può essere interrotto se si effettua il secondo passaggio:a.insert(0,[5,1,1,1])
U10-Forward
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.