2012-10-24 23 views
9

Wystąpił pewien problem przy użyciu zagnieżdżonej listy w Pythonie w pokazanym kodzie bleow.Indeksy listy zagnieżdżonej

Zasadniczo mam listę 2D zawiera wszystkie 0 wartości, chcę zaktualizować wartość listy w pętli.

Jednak Python nie daje oczekiwanego rezultatu. Czy jest coś, co źle zrozumiałem o indeksach listy Pythona i range()?

some_list = 4 * [(4 * [0])] 
for i in range(3): 
    for j in range(3): 
     some_list[i+1][j+1] = 1 
for i in range(4): 
    print(some_list[i]) 

Wyniki Spodziewałem są:

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

ale rzeczywiste wyniki Pythonie są:

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

Co tu się dzieje?

+0

Tu jest link do przewodnika na idiomatycznym programowania Python. Niektóre z nich są nieaktualne, ale część dotycząca zmiennych i nazw nadal ma znaczenie: http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#other-languages-have-variables – pcurry

Odpowiedz

19

problem jest spowodowany faktem, że Python wybiera przekazywanie list przez odniesienie.

Normalnie zmienne są przekazywane „wartością”, a więc działają one niezależnie:

>>> a = 1 
>>> b = a 
>>> a = 2 
>>> print b 
1 

Ale ponieważ list może dostać dość duże, zamiast przesuwania całą listę wokół pamięci, Python zdecyduje się po prostu użyć odniesienia ("wskaźnik" w kategoriach C). Jeśli przypiszesz jedną do drugiej zmiennej, przypisujesz do niej tylko odniesienie. Oznacza to, że można mieć dwie zmienne wskazujące na tej samej liście w pamięci:

>>> a = [1] 
>>> b = a 
>>> a[0] = 2 
>>> print b 
[2] 

Tak więc, w pierwszym wierszu kodu masz 4 * [0]. Teraz [0] jest wskaźnikiem do wartości 0 w pamięci, a po pomnożeniu otrzymujesz cztery wskaźniki w tym samym miejscu w pamięci. Ale kiedy zmienić jedną z wartości następnie Python wie, że musi zmienić wskaźnik do punktu do nowej wartości:

>>> a = 4 * [0] 
>>> a 
[0, 0, 0, 0] 
>>> [id(v) for v in a] 
[33302480, 33302480, 33302480, 33302480] 
>>> a[0] = 1 
>>> a 
[1, 0, 0, 0] 

Problem pojawia się, gdy pomnożymy tę listę - masz cztery kopie wskaźnika listy. Teraz po zmianie jednego z wartościami w jednej listy, wszystkie cztery razem zmiana:

>>> a[0][0] = 1 
>>> a 
[[1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0]] 

Rozwiązaniem jest uniknąć drugiego mnożenia. Pętla spełnia swoje zadanie:

>>> some_list = [(4 * [0]) for _ in range(4)] 
+2

Dzięki za szczegółowe wyjaśnienie! –

+6

Podczas gdy kod tutaj rozwiązuje problem OP, a wyjaśnienie poprawnie identyfikuje, że jest to spowodowane listami przekazywanymi "przez wskaźnik", nadal czuję się zmuszony do odrzucenia tylko z powodu pierwszych dwóch linii. Zmienne w Pythonie nie są "normalnie przekazywane przez wartość". Wszystko w Pythonie jest przekazywane "przez wskaźnik", ale ints i stringi (na przykład) są niezmienne, więc zachowanie Pythona w odniesieniu do tych typów może być uważane za * efektywnie równoważne * do przekazywania wartości przez prawie wszystkie rzeczywiste cele. –

+0

"Prawie" w powyższym komentarzu jest, oczywiście, ponieważ nie zwiększasz znacząco wykorzystania pamięci przez przypisanie tego samego ogromnego ciągu do wielu zmiennych lub przekazanie go do funkcji jako parametru - co zrobiłbyś, gdyby olbrzymi ciąg był przekazane według wartości. –

8

Właściwie wszystkie obiekty w liście są takie same, więc zmiana jednego zmienia innych też:

In [151]: some_list = 4 * [(4 * [0])] 

In [152]: [id(x) for x in some_list] 
Out[152]: [148641452, 148641452, 148641452, 148641452] 

In [160]: some_list[0][1]=5 #you think you changed the list at index 0 here 

In [161]: some_list 
Out[161]: [[0, 5, 0, 0], [0, 5, 0, 0], [0, 5, 0, 0], [0, 5, 0, 0]] #but all lists are changed 

tworzenia listy w ten sposób:

In [156]: some_list=[[0]*4 for _ in range(4)] 

In [157]: some_list 
Out[157]: [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] 

In [158]: [id(x) for x in some_list] 
Out[158]: [148255436, 148695180, 148258380, 148255852] 

In [163]: some_list[0][1]=5 

In [164]: some_list 
Out[164]: [[0, 5, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] #works fine in this case 
+0

Thank ty! To jest naprawdę pomocne! –

+0

@KenMa cieszę się, że pomogło. :) –

+2

Tworzysz niepotrzebne rozszerzenie do [0 dla _ w zasięgu (4)] - możesz po prostu użyć 4 * [0]. Zobacz moje wyjaśnienie w drugim poście. –

Powiązane problemy