Liste von Listenänderungen, die unerwartet in Unterlisten reflektiert werden

363

Ich musste eine Liste von Listen in Python erstellen, also tippte ich folgendes:

%Vor%

Die Liste sah so aus:

%Vor%

Dann habe ich einen der innersten Werte geändert:

%Vor%

Nun sieht meine Liste so aus:

%Vor%

was nicht das ist, was ich wollte oder erwartete. Kann jemand bitte erklären, was vor sich geht und wie man es umgehen kann?

    
Charles Anderson 27.10.2008, 14:57
quelle

13 Antworten

301

Wenn Sie [x]*3 schreiben, erhalten Sie im Wesentlichen die Liste [x, x, x] . Das heißt, eine Liste mit 3 Referenzen auf die gleiche x . Wenn Sie dann diese einzelne x ändern, ist sie über alle drei Referenzen sichtbar.

Um es zu beheben, müssen Sie sicherstellen, dass Sie an jeder Position eine neue Liste erstellen. Ein Weg, es zu tun ist

%Vor%

wird [1]*4 jedes Mal neu bewerten, anstatt es einmal zu bewerten und 3 Referenzen auf 1 Liste zu machen.

Sie fragen sich vielleicht, warum * keine unabhängigen Objekte so erstellen kann, wie das Listenverständnis es tut. Das liegt daran, dass der Multiplikationsoperator * auf Objekten operiert, ohne Ausdrücke zu sehen. Wenn Sie * verwenden, um [[1] * 4] mit 3 zu multiplizieren, sieht * nur die 1-Element-Liste [[1] * 4] , nicht den [[1] * 4 Ausdruckstext. * hat keine Ahnung, wie man Kopien dieses Elements erstellt, keine Idee, [[1] * 4] neu zu bewerten, und keine Ahnung, dass Sie sogar Kopien wollen, und im Allgemeinen gibt es möglicherweise keine Möglichkeit, das Element zu kopieren.

>

Die einzige Option * besteht darin, neue Referenzen auf die bestehende Unterliste anzulegen, anstatt neue Unterlisten zu erstellen. Alles andere wäre inkonsistent oder würde eine grundlegende Neugestaltung der grundlegenden Sprachdesign-Entscheidungen erfordern.

Im Gegensatz dazu bewertet ein Listenverständnis den Elementausdruck bei jeder Iteration neu. [[1] * 4 for n in range(3)] reevaluiert [1] * 4 jedes Mal aus demselben Grund [x**2 for x in range(3)] überprüft x**2 jedes Mal neu. Jede Auswertung von [1] * 4 erzeugt eine neue Liste, so dass das Listenverständnis funktioniert, was Sie wollen.

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

    
CAdaker 27.10.2008, 15:03
quelle
34

Genau das ist genau das, was Sie erwarten würden. Lassen Sie uns zerlegen, was hier passiert:

Sie schreiben

%Vor%

Dies entspricht:

%Vor%

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

%Vor%

As lst[0] ist nichts als lst1 .

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

%Vor%

In diesem Fall wird der Ausdruck für jedes n neu ausgewertet, was zu einer anderen Liste führt.

    
PierreBdR 27.10.2008 15:07
quelle
25
%Vor%

oder sogar:

%Vor%

Erstellt eine Liste, die auf das interne [1,1,1,1] 3 Mal verweist - nicht auf drei Kopien der inneren Liste. Wenn Sie also die Liste (an irgendeiner Position) ändern, sehen Sie die Änderung dreimal.

Es ist das gleiche wie in diesem Beispiel:

%Vor%

wo es wahrscheinlich ein wenig weniger überraschend ist.

    
Blair Conrad 27.10.2008 15:02
quelle
5

Neben der akzeptierten Antwort, die das Problem richtig erklärt hat, verwenden Sie in Ihrem Listenverständnis bei Verwendung von python-2.x xrange() , das einen effizienteren Generator zurückgibt ( range() in python 3 macht denselben Job) ) _ anstelle der Wegwerfvariablen n :

%Vor%

Auch als viel Pythonic Weg können Sie itertools.repeat() um ein Iterator-Objekt aus wiederholten Elementen zu erstellen:

%Vor%

P.S. Wenn Sie mit numpy nur ein Array von Einsen oder Nullen erstellen möchten, können Sie np.ones und np.zeros verwenden und / oder für andere Zahlen np.repeat() verwenden:

%Vor%     
Kasramvd 17.06.2015 17:08
quelle
4

In einfachen Worten geschieht dies, weil in Python alles durch Referenz funktioniert. Wenn Sie also eine Listenliste auf diese Weise erstellen, enden Sie im Grunde mit solchen Problemen.

Um Ihr Problem zu lösen, können Sie eines von beiden tun: 1. Verwenden Sie die numpy-Array Dokumentation für numpy.empty 2. Hängen Sie die Liste an, wenn Sie zu einer Liste gelangen. 3. Sie können das Wörterbuch auch verwenden, wenn Sie

möchten     
Neeraj Komuravalli 14.06.2016 06:36
quelle
2

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

%Vor%

oder

%Vor%

Wichtig hierbei ist , dass der Operator * meist verwendet wird, um eine Liste von Literalen zu erstellen. Da 1 ein Literal ist, wird daher obj =[1]*4 [1,1,1,1] erzeugen, wobei jedes 1 atomar ist und nicht eine Referenz von 1 4 mal wiederholt wird. Das heißt, wenn wir obj[2]=42 machen, dann wird obj [1,1,42,1] nicht [42,42,42,42] , wie manche vielleicht annehmen.

    
jerrymouse 06.04.2017 05:36
quelle
1

Lassen Sie uns Ihren Code folgendermaßen schreiben:

%Vor%

Führen Sie dann den folgenden Code aus, um alles klarer zu machen. Was der Code tut, druckt im Grunde die id s der erhaltenen Objekte, die

  

Geben Sie die "Identität" eines Objekts zurück

und hilft uns, sie zu identifizieren und zu analysieren, was passiert:

%Vor%

Und Sie erhalten folgende Ausgabe:

%Vor%

Lasst uns jetzt Schritt für Schritt gehen. Sie haben x , was 1 ist, und eine einzelne Elementliste y , die x enthält. Ihr erster Schritt ist y * 4 , was Ihnen eine neue Liste z liefert, was im Grunde [x, x, x, x] ist, d. H. Es erstellt eine neue Liste mit 4 Elementen, die Verweise auf das ursprüngliche x -Objekt sind. Der Netto-Schritt ist ziemlich ähnlich. Sie tun im Grunde z * 3 , das ist [[x, x, x, x]] * 3 und gibt [[x, x, x, x], [x, x, x, x], [x, x, x, x]] aus dem gleichen Grund wie für den ersten Schritt zurück.

    
bagrat 10.06.2015 14:38
quelle
1

Python-Container enthalten Verweise auf andere Objekte. Siehe dieses Beispiel:

%Vor%

In diesem b ist eine Liste, die ein Element enthält, das eine Referenz auf die Liste a ist. Die Liste a ist änderbar.

Die Multiplikation einer Liste mit einer Ganzzahl entspricht dem mehrfachen Hinzufügen der Liste zu sich selbst (siehe gemeinsame Sequenzoperationen ). Also weiter mit dem Beispiel:

%Vor%

Wir können sehen, dass die Liste c jetzt zwei Referenzen enthält, um a aufzulisten, was c = b * 2 entspricht.

Python FAQ enthält auch eine Erklärung dieses Verhaltens: Wie erstelle ich eine mehrdimensionale Liste?

    
zwn 06.04.2016 13:40
quelle
1

Ich schätze, jeder erklärt, was passiert. Ich schlage einen Weg vor, es zu lösen:

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

%Vor%

print myList

Und dann hast du:

%Vor%     
awulll 24.04.2016 13:31
quelle
1

Mit der integrierten Listenfunktion können Sie das tun

%Vor%     
anand tripathi 15.07.2016 13:48
quelle
1

Versuchen Sie, es beschreibender zu erklären,

Operation 1:

%Vor%

Operation 2:

%Vor%

Bemerken wieso änderte das Ändern des ersten Elements der ersten Liste nicht das zweite Element jeder Liste? Das liegt daran, dass [0] * 2 wirklich eine Liste mit zwei Zahlen ist und eine Referenz auf 0 nicht geändert werden kann.

Wenn Sie Klonkopien erstellen möchten, versuchen Sie Operation 3:

%Vor%

Eine weitere interessante Möglichkeit zum Erstellen von Klonkopien, Operation 4:

%Vor%     
Adil Abbasi 10.08.2016 07:09
quelle
-2

Tatsächlich, denke es in einem anderen Fall. Angenommen, Ihre Liste enthält Folgendes:

%Vor%

und wenn Sie myList[0][0] = 5 output schreiben, wird

sein %Vor%

Wie Sie erwartet haben. Aber da du deine Listenvariable wie folgt definierst:

%Vor%

Python verarbeitet Ihre Codes in diesem Muster. Wenn Sie also myList[0][0] schreiben und Ihre Liste wie oben definiert ist, wird Python sie wie [1]*3 verarbeiten. Deshalb werden alle ersten Elemente der Liste geändert.

    
GLHF 26.01.2015 08:50
quelle

Tags und Links