Seite 1 von 1

liste.append in for Schleife

Verfasst: Dienstag 4. August 2020, 17:31
von rote_schote
Hallo zusammen,
ich bin neu hier und traue mich mal, eine vermutlich sehr simple Frage zu stellen, die mich aber seit einiger Zeit beschäftigt.
wenn ich eine Liste durch die .append Funktion an eine (zunächst leere) Liste anhänge:

Code: Alles auswählen

l1 = []
l2 = [2]
l1.append(l2)
l1
so erhalte ich als Ergebnis:

Code: Alles auswählen

out: [[2]]
Wenn ich im Nachhinein die zweite Liste verändere, also und mir dann wieder die erste Liste ausgeben lasse

Code: Alles auswählen

l2 = [3]
l1
erhalte ich nach wie vor

Code: Alles auswählen

out: [[2]]
Soweit, so logisch. Er hat den Inhalt der Variable l2 in die Liste l1 gespeichert. Ein nachträgliches Ändern der Liste l2 ändert nichts am Inhalt von l1.

Wenn ich das gleiche aber in einer For-Schleife mache:

Code: Alles auswählen

l1 = []
l2 = []
for i in range(3):
	l2.append(i)
	l1.append(l2)
l1
dann bekomme ich als Ergebnis:

Code: Alles auswählen

Out: [[0, 1, 2], [0, 1, 2], [0, 1, 2]]
Python erweitert die Liste l1 also immer um den aktuellen Stand von l2 (abhängig von i), aber er ersetzt auch die bereits existierenden Listenelemente durch die neue Version.

Wenn ich die Liste in einer Variablen zwischenspeichere und sie vorher in eine Liste konvertriere, funktiert es:

Code: Alles auswählen

l1 = []
l2 = []
for i in range(3):
    l2.append(i)
    l_help = list(l2)
    l1.append(l_help)
l1
Hier kommt

Code: Alles auswählen

Out: [[0], [0, 1], [0, 1, 2]]
raus, also das was ich eigentlich erwartet habe.

Das list() macht ja aus dem Argument eine Liste. Aber gemäß type(l2) und type(l_help) sind beide bereits vom Typ Liste.

Ich habe die "Lösung" für mein Problem also im Prinzip gefunden, aber ich würde gerne verstehen, warum Python so tut wie es tut. Vielleicht kann mir jemand weiterhelfen.
Vielen Dank schon mal.

Re: liste.append in for Schleife

Verfasst: Dienstag 4. August 2020, 18:20
von __deets__
Dein Denkfehler besteht darin, dass du Namen und Objekte verwechselst.
Das ist eine Liste.

Code: Alles auswählen

foo = [1]
bar = foo
baz = [foo, bar]
Das ist eine Liste, und der Name foo zeigt auf die Liste. Danach zeigt auch der Name bar auf die immer noch gleiche Liste. Und danach gibt es eine zweite Liste, und deren *beiden* Elemente sind wiederum die erste Liste. Und das ist der entscheidende Punkt. Hier sind nur 2 Listen erzeugt worden, das von denen aus verschiedensten Stellen heraus verwiesen wird, aendert daran nichts.

Wenn du jetzt die erste Liste veraenderst, dann veraenderst du nicht die zweite Liste. Aber weil die zweimal auf die erste Liste zeigt, sieht das eben so aus.

Code: Alles auswählen

foo.append(2)
print(baz)
[[1, 2], [1, 2]]

Re: liste.append in for Schleife

Verfasst: Dienstag 4. August 2020, 18:21
von sparrow
Immer wenn du = schreibst, dann bindest du etwas an einem Namen.

Schauen wir uns mal dein erstes Beispiel an:

Code: Alles auswählen

>>> a = []
>>> b = []
>>> b.append(1)
>>> a.append(b)
>>> print(a)
[[1]]
>>> id(b)
2165605724488
>>> id(a[0])
2165605724488
>>> b = "Etwas völlig Neues"
>>> print(a)
[[1]]
>>> id(a[0])
2165605724488
>>> id(b)
2165610848688  # Die ist anders!
Fällt dir bei den IDs etwas auf?
In diesem Fall wird mit b = "Etwas völlig Neues" eine Zeichenkette an den Namen 'b' gebunden. Die Instanz des alten Objekts existiert aber noch und liegt noch als erstes Element in der Liste 'a'.

Ist das verständlich?

Re: liste.append in for Schleife

Verfasst: Dienstag 4. August 2020, 19:52
von rote_schote
Vielen Dank euch beiden. Die Kombination aus den beiden Erklärungen hat den Knoten gelöst.
An sich war mir die Sache mit den Namen schon klar, aber wenn es auf den ersten Blick so ausschaut als würde die Funktion .append() auf den Namen wirken, dann war das meinem Hirn zu viel.
Jetzt mal weitergefragt: wenn ich es explizit will, dass ein neues Objekt erzeugt wird, wie mache ich das galant? Mein list(l2) ist ja nur eine Krücke. Eine Idee wäre, eine neue Liste zu erzeugen und dann bei jedem i in der For-Schleife das jeweils j-te Element der neuen Liste gleich dem j-ten Element der Liste l2 zu setzen.

Also quasi:

Code: Alles auswählen

l1 = []
l2 = []
for i in range(3):
    l2.append(i)
    l3 = []
    for j in range(len(l2)):
        l3.append([])
        l3[j] = l2[j]
    l1.append(l3)
l1
mit der Ausgabe

Code: Alles auswählen

out: [[0], [0, 1], [0, 1, 2]]
Aber das ist sicher nicht die beste Idee dieses Jahres...
Danke nochmal!

Re: liste.append in for Schleife

Verfasst: Dienstag 4. August 2020, 22:25
von __blackjack__
@rote_schote: Ich würde sogar sagen das ist eine sehr schlechte Idee, weil das keiner versteht. Das mit dem `list()` ist keine Krücke.

Für das Beispiel würde man aber eher so etwas schreiben, denn Dein Code mit dem `list()` ist immer noch eine Spur zu umständlich:

Code: Alles auswählen

In [11]: [list(range(i)) for i in range(1, 4)]                                  
Out[11]: [[0], [0, 1], [0, 1, 2]]

Re: liste.append in for Schleife

Verfasst: Mittwoch 5. August 2020, 08:17
von Sirius3
@rote_schote: das ist sehr komplizierter Code, der an vielen Stellen nicht so günstig ist. Fangen wir in der Mitte an:

Code: Alles auswählen

        l3.append([])
        l3[j] = l2[j]
hier hängst Du erst eine leere Liste an, um diese gleich wieder mit einem anderen Wert zu überschreiben. Warum hängst Du nicht gleich den richtigen Wert an? Als Nebenbemerkung: man versucht, so weit es geht, Listenelemente zu überschreiben. Wenn man eine andere Liste möchte, erzeugt man immer eine neue Liste, mit geänderten Werten.

Code: Alles auswählen

        l3.append(l2[j])
Jetzt hat man ein innere Schleife:

Code: Alles auswählen

    l3 = []
    for j in range(len(l2)):
        l3.append(l2[j])
Jetzt ist eine Schleife über einen Index in Anti-Pattern, weil viel zu kompliziert, wenn man auch direkt über die Elemente der Liste iterieren könnte:

Code: Alles auswählen

    l3 = []
    for element in l2:
        l3.append(element)
Und diese Schleife macht nichts anderes, als eine Liste zu kopieren, das kann man einfacher mit `list` machen:

Code: Alles auswählen

l3 = list(l2)
Bleibt also:

Code: Alles auswählen

l1 = []
l2 = []
for i in range(3):
    l2.append(i)
    l3 = list(l2)
    l1.append(l3)
Jetzt ist l3 überflüssig:

Code: Alles auswählen

l1 = []
l2 = []
for i in range(3):
    l2.append(i)
    l1.append(list(l2))
Da nun l2 nur aufsteigende Zahlen enthält, kann man das auch durch die `range`-Funktion ersetzen:

Code: Alles auswählen

l1 = []
for i in range(3):
    l1.append(list(range(i)))
Und so etwas einfaches, wie eine for-Schleife, die nur ein append enthält, kann man durch eine Listcomprehension ersetzen:

Code: Alles auswählen

l1 = [list(range(i)) for i in range(3)]

Re: liste.append in for Schleife

Verfasst: Mittwoch 5. August 2020, 10:26
von __deets__
@Sirius3: dir fehlt da ein nicht in „ Als Nebenbemerkung: man versucht, so weit es geht, Listenelemente zu überschreiben.“

Re: liste.append in for Schleife

Verfasst: Mittwoch 5. August 2020, 20:59
von rote_schote
Vielen Dank euch allen. Sehr ausführlich erklärt. Habe viel gelernt.