953 Stimmen

Unerwartete Änderungen an der Liste der Listen in den Unterlisten

Ich musste eine Liste von Listen in Python erstellen, also tippte ich das Folgende ein:

my_list = [[1] * 4] * 3

Die Liste sah wie folgt aus:

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

Dann habe ich einen der innersten Werte geändert:

my_list[0][0] = 5

Jetzt sieht meine Liste wie folgt aus:

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

was nicht das ist, was ich wollte oder erwartet hatte. Kann mir bitte jemand erklären, was hier los ist und wie man das Problem umgehen kann?

0 Stimmen

Es ist zu beachten, dass die gleiche Logik für eine Liste von Dicts gilt, da das Problem des Aliasing eines veränderlichen Objekts grundsätzlich dasselbe ist. Siehe stackoverflow.com/questions/46835197/ für eine spezifischere Frage.

1 Stimmen

Gibt es spezifischere Fragen für den Fall, dass die Liste der Listen auf andere Weise erstellt wird (aber das gleiche Problem auftritt)? Zum Beispiel durch die Verwendung von .append in einer Schleife?

0 Stimmen

Siehe auch stackoverflow.com/questions/2612802 für eine Frage, die sich auf die Vermeidung dieser Art von Aliasing im Nachhinein konzentriert.

807voto

CAdaker Punkte 13449

Wenn Sie schreiben [x]*3 erhalten Sie im Wesentlichen die Liste [x, x, x] . Das heißt, eine Liste mit 3 Verweisen auf denselben x . Wenn Sie dann diese einzelne Änderung x sie ist durch alle drei Verweise auf sie sichtbar:

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

Um dies zu beheben, müssen Sie sicherstellen, dass Sie an jeder Position eine neue Liste erstellen. Eine Möglichkeit, dies zu tun, ist

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

die eine Neubewertung vornehmen wird [1]*4 jedes Mal, anstatt es einmal auszuwerten und 3 Verweise auf 1 Liste zu machen.


Sie fragen sich vielleicht, warum * kann keine unabhängigen Objekte erzeugen, wie es das Listenverständnis tut. Das liegt daran, dass der Multiplikationsoperator * wirkt auf Objekte, ohne Ausdrücke zu sehen. Wenn Sie * zu multiplizieren [[1] * 4] von 3, * sieht nur die 1-Element-Liste [[1] * 4] auswertet, nicht die [[1] * 4 Ausdruckstext. * keine Ahnung hat, wie man Kopien von diesem Element anfertigt, keine Ahnung, wie man es neu bewertet [[1] * 4] und keine Ahnung, dass Sie überhaupt Kopien wollen, und im Allgemeinen gibt es vielleicht nicht einmal eine Möglichkeit, das Element zu kopieren.

Die einzige Möglichkeit * besteht darin, neue Verweise auf die bestehende Teilliste zu erstellen, anstatt zu versuchen, neue Teillisten zu erstellen. Alles andere wäre inkonsistent oder würde eine umfassende Umgestaltung grundlegender Entscheidungen zum Sprachdesign erfordern.

Im Gegensatz dazu wird bei einem Listenverständnis der Elementausdruck bei jeder Iteration neu ausgewertet. [[1] * 4 for n in range(3)] bewertet neu [1] * 4 jedes Mal aus demselben Grund [x**2 for x in range(3)] bewertet neu x**2 jedes Mal. Jede Bewertung von [1] * 4 erzeugt eine neue Liste, so dass das Listenverständnis das tut, was Sie wollten.

Im Übrigen, [1] * 4 kopiert auch nicht die Elemente von [1] aber das spielt keine Rolle, da Ganzzahlen unveränderlich sind. Sie können nicht etwas tun wie 1.value = 2 und verwandeln eine 1 in eine 2.

45 Stimmen

Ich bin überrascht, dass niemand darauf hinweist, dass die Antwort hier irreführend ist. [x]*3 3 Referenzen speichern wie [x, x, x] ist nur richtig, wenn x ist veränderbar. Dies funktioniert nicht für z.B. a=[4]*3 , wobei nach a[0]=5 , a=[5,4,4].

69 Stimmen

Technisch gesehen ist es immer noch richtig. [4]*3 ist im Wesentlichen gleichbedeutend mit x = 4; [x, x, x] . Es stimmt allerdings, dass dies nie zu einem Schaden führen wird. Problem desde 4 unveränderlich ist. Auch Ihr anderes Beispiel ist nicht wirklich ein anderer Fall. a = [x]*3; a[0] = 5 wird keine Probleme verursachen, auch wenn x ist veränderbar, da Sie die Daten nicht ändern. x nur die Änderung a . Ich würde meine Antwort nicht als irreführend oder falsch bezeichnen - Sie haben nur kann nicht schießen Sie sich selbst in den Fuß, wenn Sie mit unveränderlichen Objekten zu tun haben.

35 Stimmen

@Allanqunzi Sie liegen falsch. Machen Sie x = 1000; lst = [x]*2; lst[0] is lst[1] -> True . Python unterscheidet hier überhaupt nicht zwischen veränderlichen und unveränderlichen Objekten.

183voto

nadrimajstor Punkte 1798
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for _ in range(size)]

Live-Visualisierung mit Python Tutor:

Frames and Objects

1 Stimmen

Also, warum, wenn wir schreiben, Matrix = [[x] * 2] macht nicht 2 elemnts für das gleiche Objekt wie das Beispiel, das Sie beschreiben, es scheint das gleiche Konzept sein, was bin ich übersehen?

1 Stimmen

@AhmedMohamed Es wird in der Tat eine Liste mit zwei Elementen desselben Objekts erstellt, die x bezieht sich auf. Wenn Sie ein globales eindeutiges Objekt mit x = object() und machen dann matrix = [[x] * 2] diese als wahr erweisen: matrix[0][0] is matrix[0][1]

0 Stimmen

@nadrimajstor Warum wirkt sich die Änderung von matrix[0] nicht auf matrix[1] aus, wie im obigen Beispiel mit der 2d-Matrix?

74voto

PierreBdR Punkte 40155

Eigentlich ist das genau das, was man erwarten würde. Lassen Sie uns aufschlüsseln, was hier passiert:

Sie schreiben

lst = [[1] * 4] * 3

Dies ist gleichbedeutend mit:

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

Dies bedeutet lst ist eine Liste mit 3 Elementen, die alle auf lst1 . Das bedeutet, dass die beiden folgenden Zeilen gleichwertig sind:

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

Como lst[0] ist nichts anderes als lst1 .

Um das gewünschte Verhalten zu erreichen, können Sie ein Listenverständnis verwenden:

lst = [ [1]*4 for n in range(3) ]

In diesem Fall wird der Ausdruck für jede n und führt zu einer anderen Liste.

0 Stimmen

Nur eine kleine Ergänzung zu der netten Antwort hier: es ist offensichtlich, dass Sie mit demselben Objekt zu tun haben, wenn Sie id(lst[0][0]) y id(lst[1][0]) oder sogar id(lst[0]) y id(lst[1])

0 Stimmen

Erklärt aber nicht, warum die Änderung einer 1d-Liste eine Kopie verursacht, während eine 2d-Liste keine Kopie verursacht

1 Stimmen

@JobHunter69 Kopieren auch nicht; es ist alles eine Illusion. Bei einer 1d-Liste mit unveränderlichen Objekten wie Zahlen oder Zeichenketten können Sie diese Listenelemente nicht ändern; Sie können sie nur durch neue Werte ersetzen. Es sieht so aus, als hätten Sie eine Kopie, obwohl Sie keine haben. Die 2d-Liste ist in Wirklichkeit eine Liste von Listen, und Sie können diese inneren Listen leicht ändern.

49voto

Blair Conrad Punkte 217777
[[1] * 4] * 3

oder sogar:

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

Erzeugt eine Liste, die auf die interne [1,1,1,1] 3 Mal - nicht drei Kopien der inneren Liste, d. h. jedes Mal, wenn Sie die Liste (an einer beliebigen Stelle) ändern, sehen Sie die Änderung drei Mal.

Es ist dasselbe wie in diesem Beispiel:

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

wo es wahrscheinlich etwas weniger überraschend ist.

3 Stimmen

Sie können den Operator "is" verwenden, um dies herauszufinden. ls[0] is ls[1] gibt True zurück.

13voto

jerrymouse Punkte 15398

my_list = [[1]*4] * 3 erstellt ein Listenobjekt [1,1,1,1] im Speicher und kopiert seine Referenz 3 Mal. Dies ist gleichbedeutend mit obj = [1,1,1,1]; my_list = [obj]*3 . Jede Änderung an obj wird an drei Stellen reflektiert, wo immer obj wird in der Liste referenziert. Die richtige Aussage wäre:

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

o

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

Ein wichtiger Punkt ist hier zu beachten ist, dass die * Betreiber ist meist verwendet, um eine Liste von Literalen . Obwohl 1 unveränderlich ist, obj = [1]*4 wird weiterhin eine Liste von 1 4 Mal wiederholt, um zu bilden [1,1,1,1] . Wenn jedoch ein Verweis auf ein unveränderliches Objekt erfolgt, wird das Objekt mit einem neuen überschrieben.

Das heißt, wenn wir obj[1] = 42 entonces obj werden [1,42,1,1] pas [42,42,42,42] wie manche vielleicht annehmen. Dies kann auch überprüft werden:

>>> my_list = [1]*4
>>> my_list
[1, 1, 1, 1]

>>> id(my_list[0])
4522139440
>>> id(my_list[1])  # Same as my_list[0]
4522139440

>>> my_list[1] = 42  # Since my_list[1] is immutable, this operation overwrites my_list[1] with a new object changing its id.
>>> my_list
[1, 42, 1, 1]

>>> id(my_list[0])
4522139440
>>> id(my_list[1])  # id changed
4522140752
>>> id(my_list[2])  # id still same as my_list[0], still referring to value `1`.
4522139440

3 Stimmen

Es geht nicht um Literale. obj[2] = 42 ersetzt die Referenz bei Index 2 im Gegensatz zur Änderung des Objekts, auf das dieser Index verweist, was die myList[2][0] = ... tut ( myList[2] eine Liste ist und die Zuweisung den Verweis bei Index 0 in dieser Liste ändert). Natürlich sind Ganzzahlen nicht veränderbar, aber viele Objekttypen sind . Und beachten Sie, dass die [....] Die Notation der Listendarstellung ist auch eine Form der literalen Syntax! Verwechseln Sie nicht zusammengesetzte (z. B. Listen) und skalare Objekte (z. B. Ganzzahlen) mit veränderlichen bzw. unveränderlichen Objekten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X