it-swarm.com.de

Liste der Listenänderungen, die sich unerwartet auf Unterlisten auswirken

Ich musste eine Liste mit Listen in Python erstellen, also habe ich Folgendes eingegeben:

myList = [[1] * 4] * 3

Die Liste sah so 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

Nun 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 erwartet hatte. Kann jemand bitte erklären, was los ist und wie man es umgehen kann?

541

Wenn Sie [x]*3 Schreiben, erhalten Sie im Wesentlichen die Liste [x, x, x]. Das heißt, eine Liste mit 3 Verweisen auf das gleiche x. Wenn Sie dann dieses einzelne x ändern, ist es über alle drei Verweise darauf sichtbar.

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 wertet [1]*4 jedes Mal neu aus, anstatt es einmal auszuwerten und 3 Verweise auf 1 Liste zu machen.


Sie fragen sich vielleicht, warum * Keine unabhängigen Objekte so erstellen kann wie das Listenverständnis. Dies liegt daran, dass der Multiplikationsoperator * Auf Objekte angewendet wird, ohne Ausdrücke zu sehen. Wenn Sie * Verwenden, um [[1] * 4] Mit 3 zu multiplizieren, wird bei * Nur die Liste mit 1 Elementen angezeigt, bei [[1] * 4] Jedoch nicht die [[1] * 4. Ausdruckstext. * Hat keine Ahnung, wie Kopien dieses Elements erstellt werden sollen, keine Ahnung, wie [[1] * 4] Neu bewertet werden soll, und keine Ahnung, dass Sie überhaupt Kopien möchten, und im Allgemeinen gibt es möglicherweise nicht einmal eine Möglichkeit zum Kopieren das Element.

Die einzige Option, die * Hat, besteht darin, neue Verweise auf die vorhandene Unterliste zu erstellen, anstatt zu versuchen, neue Unterlisten zu erstellen. Alles andere wäre inkonsistent oder erfordert eine umfassende Neugestaltung grundlegender Entscheidungen zum Sprachdesign.

Im Gegensatz dazu bewertet ein Listenverständnis den Elementausdruck bei jeder Iteration neu. [[1] * 4 for n in range(3)] wertet [1] * 4 jedes Mal aus dem gleichen Grund neu aus [x**2 for x in range(3)] wertet x**2 jedes Mal neu aus. Jede Auswertung von [1] * 4 Erzeugt eine neue Liste, so dass das Listenverständnis das tut, was Sie wollten.

Übrigens kopiert [1] * 4 Auch nicht die Elemente von [1], Aber das spielt keine Rolle, da ganze Zahlen unveränderlich sind. Sie können so etwas wie 1.value = 2 Nicht machen und aus einer 1 eine 2 machen.

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

Frames and Objects

Live Python Tutor Visualize

116
nadrimajstor

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

Du schreibst

lst = [[1] * 4] * 3

Dies ist äquivalent zu:

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

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

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

Als lst[0] Ist nichts anderes als lst1.

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

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

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

45
PierreBdR
[[1] * 4] * 3

oder auch:

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

Erstellt eine Liste, die dreimal auf das interne [1,1,1,1] Verweist - nicht drei Kopien der inneren Liste. Wenn Sie also die Liste (an einer beliebigen Position) ändern, 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 ein bisschen weniger überraschend ist.

32
Blair Conrad

Wenn Sie Python-2.x verwenden, verwenden Sie neben der akzeptierten Antwort, die das Problem richtig erklärt hat, im Rahmen Ihres Listenverständnisses xrange(), das einen effizienteren Generator zurückgibt (range() in = python 3 erledigt den gleichen Job) _ anstelle der Wegwerfvariablen n:

[[1]*4 for _ in xrange(3)]      # and in python3 [[1]*4 for _ in range(3)]

Als viel mehr pythonische Möglichkeit können Sie itertools.repeat() verwenden, um ein Iterator-Objekt von repeat zu erstellen Elemente:

>>> a=list(repeat(1,4))
[1, 1, 1, 1]
>>> a[0]=5
>>> a
[5, 1, 1, 1]

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

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
Kasrâmvd

In einfachen Worten geschieht dies, weil in python= alles nach Referenz funktioniert , wenn Sie also eine Liste mit Listen erstellen, die So landen Sie im Grunde mit solchen Problemen.

Um Ihr Problem zu lösen, können Sie einen der folgenden Schritte ausführen: 1. Verwenden Sie numpy array Dokumentation für numpy.empty 2. Fügen Sie die Liste an, wenn Sie zu einer Liste gelangen. 3. Sie können auch ein Wörterbuch verwenden, wenn Sie möchten

4

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

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

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

Die Multiplikation einer Liste mit einer ganzen Zahl 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 a enthält, was c = b * 2 Entspricht.

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

3
Zbyněk Winkler

myList = [[1]*4] * 3 erstellt ein Listenobjekt [1,1,1,1] im Speicher und kopiert die Referenz dreimal. Dies entspricht obj = [1,1,1,1]; myList = [obj]*3. Jede Änderung an obj wird an drei Stellen wiedergegeben, an denen in der Liste auf obj 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)]

Wichtig hier zu beachten ist, dass * Der Operator wird hauptsächlich zum Erstellen eines Liste der Literale verwendet. Schon seit 1 ist ein Literal, daher obj =[1]*4 wird erschaffen [1,1,1,1] wo jeder 1 ist atomar und nicht eine Referenz von 1 4 mal wiederholt. Dies bedeutet, wenn wir obj[2]=42, dann obj wird [1,1,42,1]nicht [42,42,42,42] wie manche vermuten mögen.

2
jerrymouse

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

Bemerkt, warum das erste Element der ersten Liste nicht geändert wurde und nicht das zweite Element jeder Liste geändert wurde? Das ist, weil [0] * 2 ist wirklich eine Liste mit zwei Zahlen, und ein Verweis auf 0 kann nicht geändert werden.

Wenn Sie Klonkopien erstellen möchten, versuchen Sie es mit Vorgang 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]]

eine weitere interessante Methode zum Erstellen von Klonkopien, 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]]
1
Adil Abbasi

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. Der Code gibt im Grunde genommen die id s der erhaltenen Objekte aus, die

Gibt die "Identität" eines Objekts zurück

und helfen 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 die 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 voranschreiten. Sie haben x, das ist 1, Und eine einzelne Elementliste y, die x enthält. Ihr erster Schritt ist y * 4, Wodurch Sie eine neue Liste z erhalten, die im Grunde genommen [x, x, x, x] Ist, dh, es wird eine neue Liste erstellt, die 4 Elemente enthält, auf die verwiesen wird das ursprüngliche x Objekt. Der Nettoschritt ist ziemlich ähnlich. Grundsätzlich machen Sie z * 3, Was [[x, x, x, x]] * 3 Ist und [[x, x, x, x], [x, x, x, x], [x, x, x, x]] Zurückgibt, aus dem gleichen Grund wie für den ersten Schritt.

1
bagrat

Ich denke, jeder erklärt, was passiert. Ich schlage einen Lösungsweg vor:

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

myList[0][0] = 5

print myList

Und dann hast du:

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

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
0
anand tripathi

@spelchekr from Python-Listen-Multiplikation: [[...]] * 3 erstellt 3 Listen, die sich gegenseitig spiegeln, wenn sie geändert werden und ich hatte die gleiche Frage zu "Warum erstellt nur die äußere * 3 mehr Referenzen während der innere es nicht tut? Warum ist es 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 Erzeugt ebenfalls Referenzen, aber seine Referenzen sind unveränderlich, so wie [&0, &0, &0]. Wenn Sie dann li[0] Ändern, können Sie keine zugrunde liegende Referenz von const int 0, Sie können also einfach die Referenzadresse in die neue ändern &1;
  • während ma=[&li, &li, &li] und li veränderbar sind, ist ma [0] [0] bei einem Aufruf von ma[0][0]=1 gleichbedeutend mit &li[0], also alle &li - Instanzen ändern ihre erste Adresse in &1.
0
ouxiaogu