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

Oct 27 2008

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

614 CAdaker Oct 27 2008 at 22:03

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 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] * 4jedes Mal aus dem gleichen 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.

Übrigens [1] * 4kopiert 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 = 2und aus einer 1 eine 2 machen.

140 nadrimajstor Aug 27 2013 at 06:17
size = 3
matrix_surprise = [[0] * size] * size
matrix = [[0]*size for i in range(size)]

Live Python Tutor Visualisieren

56 PierreBdR Oct 27 2008 at 22:07

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.

38 BlairConrad Oct 27 2008 at 22:02
[[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.

10 Kasravnd Jun 18 2015 at 00:08

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.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])
7 ZbyněkWinkler Apr 06 2016 at 20:40

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?

7 jerrymouse Apr 06 2017 at 12:36

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 14-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, objwird 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
5 bagrat Jun 10 2015 at 21:38

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.

5 NeerajKomuravalli Jun 14 2016 at 13:36

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

4 awulll Apr 24 2016 at 20:31

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]]
4 ouxiaogu Aug 09 2019 at 15:37

@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.
3 AdilAbbasi Aug 10 2016 at 14:09

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 AnandTripathi Jul 15 2016 at 20:48

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
2 DeepakPatankar Jun 21 2020 at 18:34

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] = 5create

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

Brian Oct 23 2020 at 02:57

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 shapeArgument numpy ) und 1.0um das Element, mit dem die Struktur initialisiert werden soll (funktioniert auch mit None). Beachten Sie, dass das initArgument 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

mishsx Nov 23 2020 at 02:45

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 [[]] * 3sind 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.