Elenco delle modifiche alle liste riflesse in modo imprevisto nelle liste secondarie
-
04-07-2019 - |
Domanda
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?
Soluzione
Quando scrivi [x] * 3
ottieni, essenzialmente, la lista [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.
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] * 4
ogni volta invece di valutarlo una volta e fare 3 riferimenti a 1 elenco.
Potresti chiederti perché *
non può creare oggetti indipendenti come fa la comprensione dell'elenco. Questo perché l'operatore di moltiplicazione *
opera su oggetti, senza vedere le espressioni. Quando usi *
per moltiplicare [[1] * 4]
per 3, *
vede solo l'elenco a 1 elemento [[ 1] * 4]
valuta, non il testo dell'espressione [[1] * 4
. *
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 per n nell'intervallo (3)]
rivaluta [1] * 4
ogni volta per lo stesso motivo [x ** 2 per x nell'intervallo (3)]
rivaluta x ** 2
ogni volta. Ogni valutazione di [1] * 4
genera un nuovo elenco, quindi la comprensione dell'elenco fa quello che volevi.
Per inciso, anche [1] * 4
non copia gli elementi di [1]
, ma non importa, poiché i numeri interi sono immutabili. Non puoi fare qualcosa come 1.value = 2
e trasformare un 1 in un 2.
Altri suggerimenti
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]
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 su lst1
. Ciò significa che le due righe seguenti sono equivalenti:
lst[0][0] = 5
lst1[0] = 5
Poiché lst [0]
non è altro che lst1
.
Per ottenere il comportamento desiderato, è possibile utilizzare la comprensione dell'elenco:
lst = [ [1]*4 for n in xrange(3) ]
In questo caso, l'espressione viene rivalutata per ogni n, portando a un elenco diverso.
[[1] * 4] * 3
o anche:
[[1, 1, 1, 1]] * 3
Crea un elenco che fa riferimento al interno [1,1,1,1]
3 volte, non a tre copie dell'elenco interno, quindi ogni volta che modifichi l'elenco (in qualsiasi posizione), vedrai il cambiamento 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.
Accanto alla risposta accettata che ha spiegato correttamente il problema, all'interno della comprensione della tua lista, se stai usando python-2.x usa 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 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]
P.S. Usando numpy, se vuoi solo creare una matrice di uno o zero puoi usare np.ones
e np.zeros
e / 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])
In parole semplici questo accade perché in Python tutto funziona per riferimento , quindi quando si crea un elenco di elenchi in questo modo si finisce sostanzialmente con tali problemi.
Per risolvere il tuo problema puoi eseguire uno di essi: 1. Usa l'array numpy documentazione per numpy.empty 2. Aggiungi l'elenco man mano che arrivi a un elenco. 3. Puoi anche usare il dizionario se vuoi
I contenitori Python contengono riferimenti ad altri oggetti. Vedi questo esempio:
>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]
In questo b
è presente un elenco che contiene un elemento che fa 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 proseguendo con l'esempio:
>>> c = b + b
>>> c
[[1], [1]]
>>>
>>> a[0] = 2
>>> c
[[2], [2]]
Possiamo vedere che l'elenco c
ora contiene due riferimenti all'elenco a
che equivale a c = b * 2
.
Le FAQ di Python contengono anche una spiegazione di questo comportamento: Come posso creare un elenco multidimensionale?
myList = [[1] * 4] * 3
crea un oggetto elenco [1,1,1,1]
in memoria e ne copia 3 volte il riferimento . Ciò equivale a obj = [1,1,1,1]; myList = [obj] * 3
. Qualsiasi modifica a obj
si rifletterà in tre punti, ovunque obj
sia 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 *
è principalmente usato per creare un elenco di letterali . Poiché 1
è un valore letterale, quindi obj = [1] * 4
creerà [1,1,1,1]
dove ogni 1
è atomico e non un riferimento di 1
ripetuto 4 volte. Questo significa che se facciamo obj [2] = 42
, obj
diventerà [1,1,42,1]
no come alcuni potrebbero supporre. [42,42,42,42[
Riscriviamo il tuo 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 id
s 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 x
che è 1
e un singolo elenco di elementi y
contenente x
. Il tuo primo passo è y * 4
che ti porterà un nuovo elenco z
, che è sostanzialmente [x, x, x, x]
, cioè crea un nuovo elenco che avrà 4 elementi, che sono riferimenti all'oggetto iniziale x
. Il passo netto è abbastanza simile. Fondamentalmente fai z * 3
, che è [[x, x, x, x]] * 3
e restituisce [[x, x, x, x ], [x, x, x, x], [x, x, x, x]]
, per lo stesso motivo del primo passo.
Immagino che tutti spieghino cosa sta succedendo. Suggerisco un modo per risolverlo:
myList = [[1 per i nell'intervallo (4)] per j nell'intervallo (3)]
myList[0][0] = 5
print myList
E poi hai:
[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
Sto 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é [0] * 2
è veramente 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]]
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