Liste der Listenänderungen, die unerwartet in Unterlisten angezeigt werden


643

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:


558

Wenn Sie schreiben, erhalten [x]*3Sie 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]*4jedes 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] * 4Ausdruckstext. *hat keine Ahnung, wie Kopien dieses Elements erstellt werden sollen, keine Ahnung, wie eine Neubewertung vorgenommen 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] * 4jedes Mal aus demselben Grund [x**2 for x in range(3)]neu bewertet, x**2jedes Mal neu bewertet . Jede Auswertung von [1] * 4generiert eine neue Liste, sodass das Listenverständnis das tut, was Sie wollten.

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


24
Ich bin überrascht, dass niemand darauf hinweist, dass die Antwort hier irreführend ist. [x]*33 Referenzen speichern wie [x, x, x]ist nur dann richtig, wenn xes veränderlich ist. Dies funktioniert nicht für zB a=[4]*3, wo nach a[0]=5,a=[5,4,4].
Allanqunzi

42
Technisch ist es immer noch richtig. [4]*3ist im Wesentlichen äquivalent zu x = 4; [x, x, x]. Es ist jedoch wahr, dass dies niemals ein Problem verursachen wird , da 4es unveränderlich ist. Auch Ihr anderes Beispiel ist nicht wirklich ein anderer Fall. a = [x]*3; a[0] = 5verursacht keine Probleme, auch wenn xes veränderlich ist, da Sie nicht modifizieren x, sondern nur modifizieren a. Ich würde meine Antwort nicht als irreführend oder falsch beschreiben - Sie können sich einfach nicht in den Fuß schießen, wenn Sie mit unveränderlichen Objekten zu tun haben.
CAdaker

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

128
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

Rahmen und Objekte

Live Python Tutor Visualisieren


Warum also, wenn wir matrix = [[x] * 2] schreiben, keine 2 Elemente für dasselbe Objekt wie das von Ihnen beschriebene Beispiel erstellen, scheint es dasselbe Konzept zu sein, was fehlt mir?
Ahmed Mohamed

@AhmedMohamed In der Tat erstellt es eine Liste mit zwei Elementen des exakt gleichen Objekts, xauf das verwiesen wird . Wenn Sie ein global eindeutiges Objekt mit x = object()matrix = [[x] * 2]matrix[0][0] is matrix[0][1]
erstellen

@nadrimajstor also, warum die Änderung in der Matrix [0] die Matrix [1] nicht beeinflusst, wie im obigen Beispiel mit der 2d-Matrix.
Ahmed Mohamed

@AhmedMohamed Überraschung kommt, wenn Sie eine "Kopie" der veränderlichen Sequenz erstellen (in unserem Beispiel ist es a list). Wenn also a row = [x] * 2als a matrix = [row] * 2beide Zeilen genau dasselbe Objekt sind und sich jetzt zu einer Zeile ändern, matrix[0][0] = yspiegeln sich diese plötzlich in der anderen(matrix[0][0] is matrix[1][0]) == True
nadrimajstor

@AhmedMohamed Schauen Sie sich Ned Batchelder - Fakten und Mythen zu Python-Namen und -Werten an, da dies möglicherweise eine bessere Erklärung bietet. :)
Nadrimajstor

52

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 lstes 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.


Nur eine kleine Ergänzung zu der schönen Antwort hier: Es ist offensichtlich, dass Sie mit dem gleichen Objekt zu tun haben, wenn Sie es tun id(lst[0][0])und id(lst[1][0])oder sogar id(lst[0])undid(lst[1])
Sergiy Kolodyazhnyy

36
[[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 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.


3
Sie können den Operator "is" verwenden, um dies zu ermitteln. ls [0] ist ls [1] gibt True zurück.
Mipadi

9

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) _als die Wegwerfvariable 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.onesund np.zerosund / 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])

6

myList = [[1]*4] * 3Erstellt 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 objwird an drei Stellen wiedergegeben, wo objimmer 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 1unveränderlich, obj =[1]*4wird immer noch eine Liste von 1viermal wiederholten zu erstellen [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, objwird dies [1,42,1,1] nicht so sein, [42,42,42,42] wie manche annehmen. Dies kann auch überprüft werden:

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

2
Es geht nicht um Literale. obj[2] = 42 die Referenz ersetzt bei Index 2, wie zum Mutieren des durch diesen Index referenzierten Objekts gegenüber , das ist , was myList[2][0] = ...bedeutet ( myList[2]eine Liste, und die assigment Abspaltungen die Referenz bei Index 0 in THA - Liste). Natürlich sind ganze Zahlen nicht wandelbar, aber viele Objekttypen sind . Und beachten Sie, dass die [....]Notation der Listenanzeige auch eine Form der wörtlichen Syntax ist! Verwechseln Sie keine zusammengesetzten (wie Listen) und skalaren Objekte (wie Ganzzahlen) mit veränderlichen oder unveränderlichen Objekten.
Martijn Pieters

5

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

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

In dieser bListe befindet sich ein Element, das auf die Liste verweist a. Die Liste aist 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 cjetzt zwei Verweise auf die Liste enthält, adie äquivalent zu sind c = b * 2.

Die Python-FAQ enthält auch Erklärungen zu diesem Verhalten: Wie erstelle ich eine mehrdimensionale Liste?


5

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


2

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 xwelche 1und eine einzelne Elementliste yenthä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 xObjekt verweisen . Der Netzschritt ist ziemlich ähnlich. Sie tun im Grunde z * 3, was ist [[x, x, x, x]] * 3und zurückkehrt [[x, x, x, x], [x, x, x, x], [x, x, x, x]], aus dem gleichen Grund wie für den ersten Schritt.


2

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

2

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] * 2wirklich 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]]

2

@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 *3erstellt auch Referenzen, aber seine Referenzen sind unveränderlich. [&0, &0, &0]Wenn li[0]Sie sie ändern möchten 0, 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]und liist veränderlich. Wenn Sie also aufrufen ma[0][0]=1, ist ma [0] [0] gleich &li[0], sodass alle &liInstanzen ihre erste Adresse in ändern &1.

1

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

1
Beachten Sie, dass der vierte Schritt fallengelassen werden kann, wenn Sie den zweiten Schritt a.insert(0,[5,1,1,1])
ausführen
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.