Liste der Listenänderungen, die unerwartet in Unterlisten angezeigt werden
Ich musste eine Liste von Listen in Python erstellen, also gab ich Folgendes ein:
myList = [[1] * 4] * 3
Die Liste sah folgendermaßen aus:
[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
Dann habe ich einen der innersten Werte geändert:
myList[0][0] = 5
Jetzt sieht meine Liste so aus:
[[5, 1, 1, 1], [5, 1, 1, 1], [5, 1, 1, 1]]
Das ist nicht das, was ich wollte oder erwartete. Kann jemand bitte erklären, was los ist und wie man es umgeht?
Antworten
Wenn Sie schreiben, erhalten [x]*3
Sie im Wesentlichen die Liste [x, x, x]
. Das heißt, eine Liste mit 3 Verweisen darauf x
. Wenn Sie diese Single dann ändern x
, ist sie über alle drei Verweise darauf 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. Ein Weg, dies zu tun, ist
[[1]*4 for _ in range(3)]
Dies wird [1]*4
jedes Mal neu bewertet, anstatt es einmal zu bewerten und 3 Verweise auf 1 Liste zu machen.
Sie fragen sich vielleicht, warum *
unabhängige Objekte nicht so erstellt werden können wie das Listenverständnis. Dies liegt daran, dass der Multiplikationsoperator *
Objekte bearbeitet, ohne Ausdrücke zu sehen. Wenn Sie mit 3 *
multiplizieren [[1] * 4]
, wird *
nur die 1-Element-Liste [[1] * 4]
ausgewertet, nicht der [[1] * 4
Ausdruckstext. *
hat keine Ahnung, wie Kopien dieses Elements erstellt werden sollen, keine Ahnung, wie eine Neubewertung durchgeführt werden soll [[1] * 4]
, und keine Ahnung, dass Sie überhaupt Kopien wünschen, und im Allgemeinen gibt es möglicherweise nicht einmal eine Möglichkeit, das Element zu kopieren.
Die einzige Möglichkeit *
besteht darin, neue Verweise auf die vorhandene Unterliste zu erstellen, anstatt zu versuchen, neue Unterlisten zu erstellen. Alles andere wäre inkonsistent oder würde eine umfassende Neugestaltung grundlegender Entscheidungen zum Sprachdesign erfordern.
Im Gegensatz dazu bewertet ein Listenverständnis den Elementausdruck bei jeder Iteration neu. [[1] * 4 for n in range(3)]
wird [1] * 4
jedes Mal aus dem gleichen Grund [x**2 for x in range(3)]
neu bewertet, x**2
jedes Mal neu bewertet . Jede Auswertung von [1] * 4
generiert eine neue Liste, sodass das Listenverständnis das tut, was Sie wollten.
Übrigens [1] * 4
kopiert auch nicht die Elemente von [1]
, aber das spielt keine Rolle, da Ganzzahlen unveränderlich sind. Sie können so etwas nicht tun 1.value = 2
und aus einer 1 eine 2 machen.
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]
Live Python Tutor Visualisieren
Genau das würden Sie erwarten. Zerlegen wir, was hier passiert:
Du schreibst
lst = [[1] * 4] * 3
Dies entspricht:
lst1 = [1]*4
lst = [lst1]*3
Dies bedeutet, dass lst
es sich um eine Liste mit 3 Elementen handelt, auf die alle verweisen lst1
. Dies bedeutet, dass die beiden folgenden Zeilen äquivalent sind:
lst[0][0] = 5
lst1[0] = 5
Da lst[0]
ist nichts als lst1
.
Um das gewünschte Verhalten zu erhalten, können Sie das Listenverständnis verwenden:
lst = [ [1]*4 for n in range(3) ] #python 3
lst = [ [1]*4 for n in xrange(3) ] #python 2
In diesem Fall wird der Ausdruck für jedes n neu ausgewertet, was zu einer anderen Liste führt.
[[1] * 4] * 3
oder auch:
[[1, 1, 1, 1]] * 3
Erstellt eine Liste, die [1,1,1,1]
dreimal auf die interne Liste verweist - nicht auf drei Kopien der inneren Liste. Jedes Mal, wenn Sie die Liste ändern (an einer beliebigen Position), wird die Änderung dreimal angezeigt.
Es ist das gleiche 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.
Wenn Sie Python-2.x verwenden xrange()
, wird neben der akzeptierten Antwort, die das Problem korrekt erklärt hat, innerhalb Ihres Listenverständnisses ein Generator zurückgegeben, der effizienter ist ( range()
in Python 3 erledigt er den gleichen Job), _
anstatt der Wegwerfvariablen n
:
[[1]*4 for _ in xrange(3)] # and in python3 [[1]*4 for _ in range(3)]
Als viel pythonischere Methode können Sie auch itertools.repeat()ein Iteratorobjekt aus wiederholten Elementen erstellen:
>>> a=list(repeat(1,4))
[1, 1, 1, 1]
>>> a[0]=5
>>> a
[5, 1, 1, 1]
PS Mit numpy, wenn Sie eine Reihe von Einsen oder Nullen erstellen möchten , können Sie verwenden , np.ones
und np.zeros
und / oder für andere Nummer verwenden 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])
Python-Container enthalten Verweise auf andere Objekte. Siehe dieses Beispiel:
>>> a = []
>>> b = [a]
>>> b
[[]]
>>> a.append(1)
>>> b
[[1]]
In dieser b
Liste befindet sich ein Element, das auf die Liste verweist a
. Die Liste a
ist veränderlich.
Die Multiplikation einer Liste mit einer Ganzzahl entspricht dem mehrfachen Hinzufügen der Liste zu sich selbst (siehe allgemeine Sequenzoperationen ). Fahren Sie also mit dem Beispiel fort:
>>> c = b + b
>>> c
[[1], [1]]
>>>
>>> a[0] = 2
>>> c
[[2], [2]]
Wir können sehen, dass die Liste c
jetzt zwei Verweise auf die Liste enthält, a
die äquivalent zu sind c = b * 2
.
Die Python-FAQ enthält auch Erklärungen zu diesem Verhalten: Wie erstelle ich eine mehrdimensionale Liste?
myList = [[1]*4] * 3
Erstellt ein Listenobjekt [1,1,1,1]
im Speicher und kopiert seine Referenz dreimal. Dies entspricht obj = [1,1,1,1]; myList = [obj]*3
. Jede Änderung an obj
wird an drei Stellen wiedergegeben, wo obj
immer in der Liste darauf verwiesen wird. Die richtige Aussage wäre:
myList = [[1]*4 for _ in range(3)]
oder
myList = [[1 for __ in range(4)] for _ in range(3)]
Hierbei ist zu beachten, dass der *
Operator hauptsächlich zum Erstellen einer Liste von Literalen verwendet wird . Obwohl 1
unveränderlich, obj =[1]*4
wird immer noch eine Liste von 1
4-mal wiederholt zu bilden [1,1,1,1]
. Wenn jedoch auf ein unveränderliches Objekt Bezug genommen wird, wird das Objekt mit einem neuen überschrieben.
Dies bedeutet, wenn wir dies tun obj[1]=42
, obj
wird dies [1,42,1,1]
nicht so sein,
wie manche annehmen. Dies kann auch überprüft werden:
[42,42,42,42]
>>> 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
Lassen Sie uns Ihren Code folgendermaßen umschreiben:
x = 1
y = [x]
z = y * 4
myList = [z] * 3
Führen Sie dann den folgenden Code aus, um alles klarer zu machen. Was der Code tut, ist im Grunde das ids der erhaltenen Objekte zu drucken , die
Gibt die "Identität" eines Objekts zurück
und hilft uns, sie zu identifizieren und zu analysieren, was passiert:
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)))
Und Sie erhalten folgende Ausgabe:
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
Lassen Sie uns nun Schritt für Schritt gehen. Sie haben x
welche 1
und eine einzelne Elementliste y
enthält x
. Ihr erster Schritt besteht darin y * 4
, eine neue Liste zu erhalten z
, [x, x, x, x]
dh im Grunde genommen wird eine neue Liste erstellt, die 4 Elemente enthält, die auf das ursprüngliche x
Objekt verweisen . Der Netzschritt ist ziemlich ähnlich. Sie tun im Grunde z * 3
, was ist [[x, x, x, x]] * 3
und zurückkehrt [[x, x, x, x], [x, x, x, x], [x, x, x, x]]
, aus dem gleichen Grund wie für den ersten Schritt.
In einfachen Worten, dies geschieht, weil in Python alles als Referenz funktioniert. Wenn Sie also eine Liste mit Listen auf diese Weise erstellen, treten im Grunde solche Probleme auf.
Um Ihr Problem zu lösen, können Sie eine der folgenden Aktionen ausführen : 1. Verwenden Sie die numpy-Array- Dokumentation für numpy.empty. 2. Hängen Sie die Liste an, sobald Sie zu einer Liste gelangen. 3. Sie können auch das Wörterbuch verwenden, wenn Sie möchten
Ich denke, jeder erklärt, was passiert. Ich schlage einen Weg vor, um es zu lösen:
myList = [[1 for i in range(4)] for j in range(3)]
myList[0][0] = 5
print myList
Und dann haben Sie:
[[5, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]
@spelchekr aus der Python- Listenmultiplikation : [[...]] * 3 erstellt 3 Listen, die sich bei Änderungen gegenseitig spiegeln, und ich hatte die gleiche Frage zu "Warum erstellt nur die äußere * 3 mehr Referenzen, während die innere nicht ? Warum ist nicht alles 1s? "
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]]
Hier ist meine Erklärung, nachdem ich den obigen Code ausprobiert habe:
- Das Innere
*3
erstellt auch Referenzen, aber seine Referenzen sind unveränderlich.[&0, &0, &0]
Wennli[0]
Sie sie ändern möchten0
, können Sie keine zugrunde liegende Referenz von const int ändern. Sie können also einfach die Referenzadresse in die neue ändern&1
. - while
ma=[&li, &li, &li]
undli
ist veränderlich. Wenn Sie also aufrufenma[0][0]=1
, ist ma [0] [0] gleich&li[0]
, sodass alle&li
Instanzen ihre erste Adresse in ändern&1
.
Der Versuch, es anschaulicher zu erklären,
Operation 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]]
Operation 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]]
Es wurde festgestellt, warum durch Ändern des ersten Elements der ersten Liste nicht das zweite Element jeder Liste geändert wurde. Das liegt daran, dass es sich [0] * 2
wirklich um eine Liste mit zwei Zahlen handelt und ein Verweis auf 0 nicht geändert werden kann.
Wenn Sie Klonkopien erstellen möchten, versuchen Sie Operation 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]]
Ein weiterer interessanter Weg, um Klonkopien zu erstellen, ist Operation 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]]
Mit der eingebauten Listenfunktion können Sie dies tun
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
Diese Fragen haben viele Antworten, ich füge meine Antwort hinzu, um dasselbe schematisch zu erklären.
Durch die Art und Weise, wie Sie 2D erstellt haben, wird eine flache Liste erstellt
arr = [[0]*cols]*row
Wenn Sie stattdessen die Elemente der Liste aktualisieren möchten, sollten Sie verwenden
rows, cols = (5, 5)
arr = [[0 for i in range(cols)] for j in range(rows)]
Erklärung :
Man kann eine Liste erstellen mit:
arr = [0]*N
oder
arr = [0 for i in range(N)]
Im ersten Fall zeigen alle Indizes des Arrays auf dasselbe ganzzahlige Objekt
und wenn Sie einem bestimmten Index einen Wert zuweisen, wird ein neues int-Objekt erstellt, z. B. arr[4] = 5
create
Lassen Sie uns nun sehen, was passiert, wenn wir eine Liste mit Listen erstellen. In diesem Fall zeigen alle Elemente unserer Top-Liste auf dieselbe Liste
Wenn Sie den Wert eines Index aktualisieren, wird ein neues int-Objekt erstellt. Da jedoch alle Listenindizes der obersten Ebene auf dieselbe Liste zeigen, sehen alle Zeilen gleich aus. Und Sie werden das Gefühl haben, dass beim Aktualisieren eines Elements alle Elemente in dieser Spalte aktualisiert werden.
Credits: Vielen Dank an Pranav Devarakonda für die einfache Erklärung hier
Ich bin hier angekommen, weil ich gesucht habe, wie ich eine beliebige Anzahl von Listen verschachteln kann. Es gibt viele Erklärungen und spezifische Beispiele oben, aber Sie können die N-dimensionale Liste von Listen von Listen von ... mit der folgenden rekursiven Funktion verallgemeinern:
import copy
def list_ndim(dim, el=None, init=None):
if init is None:
init = el
if len(dim)> 1:
return list_ndim(dim[0:-1], None, [copy.copy(init) for x in range(dim[-1])])
return [copy.deepcopy(init) for x in range(dim[0])]
Sie rufen die Funktion zum ersten Mal folgendermaßen auf:
dim = (3,5,2)
el = 1.0
l = list_ndim(dim, el)
Dabei (3,5,2)
handelt es sich um ein Tupel der Dimensionen der Struktur (ähnlich dem shape
Argument numpy ) und 1.0
um das Element, mit dem die Struktur initialisiert werden soll (funktioniert auch mit None). Beachten Sie, dass das init
Argument nur durch den rekursiven Aufruf zum Übertragen der verschachtelten untergeordneten Listen bereitgestellt wird
Ausgabe von oben:
[[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]],
[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]],
[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]]]
bestimmte Elemente festlegen:
l[1][3][1] = 56
l[2][2][0] = 36.0+0.0j
l[0][1][0] = 'abc'
resultierende Ausgabe:
[[[1.0, 1.0], ['abc', 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 1.0]],
[[1.0, 1.0], [1.0, 1.0], [1.0, 1.0], [1.0, 56.0], [1.0, 1.0]],
[[1.0, 1.0], [1.0, 1.0], [(36+0j), 1.0], [1.0, 1.0], [1.0, 1.0]]]
Die nicht typisierte Natur von Listen wird oben gezeigt
Beachten Sie, dass Elemente in der Sequenz nicht kopiert werden. Sie werden mehrfach referenziert . Dies verfolgt häufig neue Python-Programmierer. Erwägen:
>>> lists = [[]] * 3
>>> lists
[[], [], []]
>>> lists[0].append(3)
>>> lists
[[3], [3], [3]]
Was passiert ist, ist, dass [[]]
es sich um eine Ein-Element-Liste handelt, die eine leere Liste enthält. Alle drei Elemente von [[]] * 3
sind also Verweise auf diese einzelne leere Liste. Durch Ändern eines der Elemente von Listen wird diese einzelne Liste geändert.
Ein weiteres Beispiel, um dies zu erklären, ist die Verwendung mehrdimensionaler Arrays .
Sie haben wahrscheinlich versucht, ein mehrdimensionales Array wie folgt zu erstellen:
>>> A = [[**None**] * 2] * 3
Dies sieht korrekt aus, wenn Sie es drucken:
>>> A
[[None, None], [None, None], [None, None]]
Wenn Sie jedoch einen Wert zuweisen, wird dieser an mehreren Stellen angezeigt:
>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]
Der Grund dafür ist, dass beim Replizieren einer Liste *
keine Kopien erstellt werden, sondern nur Verweise auf die vorhandenen Objekte. Die 3 erstellt eine Liste mit 3 Verweisen auf dieselbe Liste der Länge zwei. Änderungen an einer Zeile werden in allen Zeilen angezeigt, was mit ziemlicher Sicherheit nicht Ihren Wünschen entspricht.