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?

È stato utile?

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

Cornici e oggetti

Tutor Live Python Visualizza

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 [42,42,42,42[ come alcuni potrebbero supporre.

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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top