Lista zmian list odzwierciedlona nieoczekiwanie na listach podrzędnych

644

Musiałem utworzyć listę list w Pythonie, więc wpisałem:

myList = [[1] * 4] * 3

Lista wyglądała następująco:

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

Następnie zmieniłem jedną z najbardziej wewnętrznych wartości:

myList[0][0] = 5

Teraz moja lista wygląda następująco:

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

co nie jest tym, czego chciałem lub oczekiwałem. Czy ktoś może wyjaśnić, co się dzieje i jak to obejść?

Charles Anderson
źródło

Odpowiedzi:

559

Kiedy piszesz [x]*3, dostajesz w zasadzie listę [x, x, x]. To znaczy lista z 3 odniesieniami do tego samego x. Kiedy następnie zmodyfikujesz ten singiel, xbędzie on widoczny poprzez wszystkie trzy odniesienia do niego:

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

Aby to naprawić, musisz utworzyć nową listę w każdej pozycji. Jednym ze sposobów na to jest

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

który za [1]*4każdym razem będzie ponownie oceniać, zamiast oceniać go raz i czyniąc 3 odniesienia do 1 listy.


Możesz się zastanawiać, dlaczego *nie można tworzyć niezależnych obiektów w taki sposób, jak robi to lista. Jest tak, ponieważ operator mnożenia *działa na obiektach, nie widząc wyrażeń. Kiedy używasz *do pomnożenia [[1] * 4]przez 3, *widzi tylko listę 1-elementową [[1] * 4], a nie [[1] * 4tekst wyrażenia. *nie ma pojęcia, jak wykonać kopię tego elementu, nie ma pojęcia, jak dokonać ponownej oceny [[1] * 4], i nie ma pojęcia, że ​​chcesz kopii, a ogólnie rzecz biorąc, może nie być nawet sposobu na skopiowanie elementu.

Jedyną opcją *jest tworzenie nowych odniesień do istniejącej listy podrzędnej zamiast prób tworzenia nowych list podrzędnych. Wszystko inne byłoby niespójne lub wymagałoby poważnego przeprojektowania podstawowych decyzji dotyczących projektowania języka.

W przeciwieństwie do tego, zrozumienie listy ponownie ocenia wyrażenie elementu przy każdej iteracji. [[1] * 4 for n in range(3)]ponownej waloryzacji [1] * 4za każdym razem z tego samego powodu [x**2 for x in range(3)]ponownej waloryzacji x**2za każdym razem. Każda ocena [1] * 4generuje nową listę, więc zrozumienie listy robi to, co chciałeś.

Nawiasem mówiąc, [1] * 4również nie kopiuje elementów [1], ale to nie ma znaczenia, ponieważ liczby całkowite są niezmienne. Nie możesz zrobić czegoś takiego 1.value = 2i zmienić 1 w 2.

CAdaker
źródło
24
Dziwi mnie, że żadne ciało tego nie wskazuje, odpowiedź tutaj jest myląca. [x]*3przechowuj 3 odnośniki, tak jak [x, x, x]jest właściwe tylko wtedy, gdy xmożna je modyfikować. To dzieło nie podlega na przykład a=[4]*3, gdzie po a[0]=5,a=[5,4,4].
Allanqunzi
42
Technicznie jest to nadal poprawne. [4]*3jest zasadniczo równoważne z x = 4; [x, x, x]. Prawdą jest jednak, że nigdy nie spowoduje to żadnego problemu, ponieważ 4jest niezmienny. Twój drugi przykład nie jest tak naprawdę innym przypadkiem. a = [x]*3; a[0] = 5nie spowoduje problemów, nawet jeśli xmożna je modyfikować x, ponieważ nie modyfikujesz , tylko modyfikujesz a. Nie opisałbym mojej odpowiedzi jako wprowadzającej w błąd lub błędnej - po prostu nie możesz strzelić sobie w stopę, jeśli masz do czynienia z niezmiennymi przedmiotami.
CAdaker
19
@Allanqunzi jesteś w błędzie. Czy x = 1000; lst = [x]*2; lst[0] is lst[1]-> True. Python nie rozróżnia tutaj w żaden sposób obiektów zmiennych i niezmiennych.
timgeb
129
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

Ramki i obiekty

Wizualizacja Live Python Tutor

nadrimajstor
źródło
Dlaczego więc, jeśli napiszemy matrix = [[x] * 2] nie tworzy 2 elementów dla tego samego obiektu, jak w opisanym przykładzie, wydaje się, że to ta sama koncepcja, czego mi brakuje?
Ahmed Mohamed
@ AhmedMohamed Rzeczywiście tworzy listę z dwoma elementami tego samego obiektu, xdo którego się odnosi. Jeśli x = object()matrix = [[x] * 2]matrix[0][0] is matrix[0][1]
stworzysz
@nadrimajstor, więc dlaczego zmiana macierzy [0] nie wpływa na macierz [1] jak w powyższym przykładzie z macierzą 2d.
Ahmed Mohamed
@ AhmedMohamed Niespodzianka przychodzi, kiedy tworzysz „kopię” zmiennej sekwencji (w naszym przykładzie jest to list), więc jeśli row = [x] * 2a następnie matrix = [row] * 2gdzie oba rzędy są dokładnie tym samym obiektem, a teraz zmiany w jednym rzędzie matrix[0][0] = ynagle odzwierciedlają się w drugim(matrix[0][0] is matrix[1][0]) == True
nadrimajstor
@AhmedMohamed Spójrz na Ned Batchelder - Fakty i mity na temat nazw i wartości Pythona, ponieważ może to stanowić lepsze wyjaśnienie. :)
nadrimajstor
52

W rzeczywistości jest to dokładnie to, czego można oczekiwać. Rozłóżmy to, co się tutaj dzieje:

Ty piszesz

lst = [[1] * 4] * 3

Jest to równoważne z:

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

Oznacza to, że lstjest to lista z 3 elementami wskazującymi na lst1. Oznacza to, że dwie następujące linie są równoważne:

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

Jak lst[0]nic lst1.

Aby uzyskać pożądane zachowanie, możesz skorzystać ze zrozumienia listy:

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

W takim przypadku wyrażenie jest ponownie oceniane dla każdego n, co prowadzi do innej listy.

PierreBdR
źródło
Mały dodatek do ładnej odpowiedzi tutaj: oczywiste jest, że masz do czynienia z tym samym przedmiotem, jeśli tak, id(lst[0][0])a id(lst[1][0])nawetid(lst[0])id(lst[1])
Sergiy Kolodyazhnyy
36
[[1] * 4] * 3

lub nawet:

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

Tworzy listę, która odwołuje się do wewnętrznej [1,1,1,1]3 razy - nie do trzech kopii wewnętrznej listy, więc za każdym razem, gdy zmodyfikujesz listę (w dowolnej pozycji), zobaczysz zmianę trzy razy.

Jest taki sam jak w tym przykładzie:

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

gdzie jest to chyba trochę mniej zaskakujące.

Blair Conrad
źródło
3
Aby to odkryć, możesz użyć operatora „jest”. ls [0] to ls [1] zwraca wartość True.
mipadi
9

Oprócz zaakceptowanej odpowiedzi, która poprawnie wyjaśniła problem, w ramach twojej listy, jeśli używasz python-2.x użyj, xrange()która zwraca generator, który jest bardziej wydajny ( range()w python 3 wykonuje to samo zadanie) _zamiast zmiennej jednorazowej n:

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

Ponadto, jako o wiele bardziej Pythoński sposób, możesz użyć itertools.repeat()do utworzenia obiektu iteratora powtarzających się elementów:

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

PS Korzystanie numpy, jeśli chcesz tylko utworzyć tablicę jedynek lub zer można użyć np.onesa np.zerosi / lub o innym przeznaczeniu numer 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])
Kasramvd
źródło
6

Kontenery Python zawierają odniesienia do innych obiektów. Zobacz ten przykład:

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

Na tej bliście znajduje się jeden element, który jest odniesieniem do listy a. Lista ajest zmienna.

Mnożenie listy przez liczbę całkowitą jest równoważne wielokrotnemu dodawaniu listy do siebie (patrz typowe operacje na sekwencjach ). Kontynuując przykład:

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

Widzimy, że lista czawiera teraz dwa odniesienia do listy, aktóre są równoważne c = b * 2.

Python FAQ zawiera także wyjaśnienie tego zachowania: Jak utworzyć listę wielowymiarową?

Zbyněk Winkler
źródło
6

myList = [[1]*4] * 3tworzy jeden obiekt listy [1,1,1,1]w pamięci i trzykrotnie kopiuje jego odwołanie. Jest to równoważne z obj = [1,1,1,1]; myList = [obj]*3. Wszelkie modyfikacje objzostaną odzwierciedlone w trzech miejscach, niezależnie od tego, gdzie objznajduje się odniesienie na liście. Właściwym stwierdzeniem byłoby:

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

lub

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

Ważną rzeczą do zapamiętania jest to, że *operator jest najczęściej używany do tworzenia listy literałów . Chociaż 1jest niezmienny, obj =[1]*4nadal tworzy listę 1powtarzanych 4 razy do utworzenia [1,1,1,1]. Ale jeśli pojawi się jakakolwiek wzmianka o niezmiennym obiekcie, obiekt zostanie nadpisany nowym.

Oznacza to, że jeśli to zrobimy obj[1]=42, nie objstanie się [1,42,1,1] tak, [42,42,42,42] jak niektórzy mogą przypuszczać. Można to również zweryfikować:

>>> 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
jerrymouse
źródło
2
Tu nie chodzi o literały. obj[2] = 42 zastępuje odwołanie w indeksie 2, w przeciwieństwie do mutowania obiektu, do którego odwołuje się ten indeks, co właśnie myList[2][0] = ...robi ( myList[2]jest listą, a przypisanie zmienia referencję w indeksie 0 na liście). Oczywiście liczby całkowite nie są zmienne, ale istnieje wiele typów obiektów . I zauważ, że [....]notacja wyświetlania listy jest również formą dosłownej składni! Nie należy mylić obiektów złożonych (takich jak listy) i skalarnych (takich jak liczby całkowite) z obiektami zmiennymi i niezmiennymi.
Martijn Pieters
5

Krótko mówiąc, dzieje się tak, ponieważ w Pythonie wszystko działa przez odniesienie , więc kiedy tworzysz listę w ten sposób, w zasadzie masz takie problemy.

Aby rozwiązać problem, możesz wykonać jedną z nich: 1. Użyj dokumentacji tablic numpy dla numpy.empty 2. Dołącz listę, gdy przejdziesz do listy. 3. Możesz także użyć słownika, jeśli chcesz

Neeraj Komuravalli
źródło
2

Pozwól nam przepisać Twój kod w następujący sposób:

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

myList = [z] * 3

Następnie uruchom następujący kod, aby wszystko było bardziej jasne. To, co robi kod, to w zasadzie wypisywanie ids uzyskanych obiektów, które

Zwraca „tożsamość” obiektu

i pomoże nam je zidentyfikować i przeanalizować, co się stanie:

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

Otrzymasz następujące dane wyjściowe:

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

Przejdźmy teraz krok po kroku. Masz, xco jest 1, i listę pojedynczych elementów yzawierającą x. Pierwszym krokiem jest y * 4uzyskanie nowej listy z, która jest w zasadzie [x, x, x, x], tzn. Tworzy nową listę, która będzie zawierać 4 elementy, które są odniesieniami do początkowego xobiektu. Krok netto jest dość podobny. Zasadniczo robisz to z * 3, co jest [[x, x, x, x]] * 3i powraca [[x, x, x, x], [x, x, x, x], [x, x, x, x]], z tego samego powodu, co dla pierwszego kroku.

bagrat
źródło
2

Chyba wszyscy tłumaczą, co się dzieje. Sugeruję jeden ze sposobów rozwiązania tego problemu:

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

myList[0][0] = 5

print myList

A potem masz:

[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
awulll
źródło
2

Próbując wyjaśnić to bardziej opisowo,

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

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

Zauważyłeś, dlaczego modyfikowanie pierwszego elementu pierwszej listy nie modyfikowało drugiego elementu każdej listy? Jest tak, ponieważ [0] * 2tak naprawdę jest to lista dwóch liczb, a odniesienia do 0 nie można modyfikować.

Jeśli chcesz utworzyć klonowane kopie, wypróbuj Operację 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]]

inny ciekawy sposób tworzenia kopii klonowanych, Operacja 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]]
Adil Abbasi
źródło
2

@spelchekr z mnożenia listy w Pythonie: [[...]] * 3 tworzy 3 listy, które po zmodyfikowaniu odzwierciedlają się nawzajem, a ja miałem to samo pytanie dotyczące „Dlaczego tylko zewnętrzne * 3 tworzą więcej referencji, podczas gdy wewnętrzne nie „Dlaczego to nie wszystkie 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]]

Oto moje wyjaśnienie po wypróbowaniu powyższego kodu:

  • Wewnętrzne *3tworzy również referencje, ale jego referencje są niezmienne, coś w rodzaju [&0, &0, &0]: wtedy, kiedy się zmienia li[0], nie można zmienić żadnego bazowego odwołania const int 0, więc wystarczy zmienić adres referencyjny na nowy &1;
  • while ma=[&li, &li, &li]i limożna go modyfikować, więc gdy zadzwonisz ma[0][0]=1, ma [0] [0] jest równe &li[0], więc wszystkie &liinstancje zmienią swój pierwszy adres na &1.
ouxiaogu
źródło
1

Korzystając z wbudowanej funkcji listy, możesz to zrobić w ten sposób

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
anand tripathi
źródło
1
Uwaga: czwarty krok można pominąć, jeśli zrobisz drugi krok:a.insert(0,[5,1,1,1])
U10-Forward