Seite 1 von 2

Liste in Liste-Problem

Verfasst: Mittwoch 3. Februar 2021, 12:18
von NWA
Hallo, ich bin Python-Neuling und bin über ein Problem gestolpert.
Ich möchte gerne eine Liste mit inneren Listen in eine neue Liste kopieren (Simulation eines streams).
An die letzte innere Liste soll immer ein Wert angehängt werden.
Ich lass also die Quell-Liste mit einer for-Schleife durchlaufen und hänge die inneren Listen an die Ziel-Liste an (erste .append).
An die so entstehende letzte Liste hänge ich immer einen Wert (99) an.

Das Problem:
Wieso wird die 99 auch der Quell-Liste hinzugefügt?

Das ist so als wenn ich dem Läufer aus der for-Schleife eine Zahl anhänge (x.append(99))


Code: Alles auswählen

quelle = [[1,5],[2,5],[3,5]] #Vorgaben-Liste

ziel = [] #Datenliste zum Speichern der Daten aus der Vorgabenliste

for innere_liste in quelle:
    ziel.append(innere_liste) #Element "speichern"
    ziel[len(ziel)-1].append(99) ##innere data-Liste erweitern
#    innere_liste.append(99)
    print("quelle: ", quelle)
    print("ziel  : ", ziel)
 

Code: Alles auswählen

Ausgabe

quelle:  [[1, 5, 99], [2, 5], [3, 5]]
ziel  :  [[1, 5, 99]]
quelle:  [[1, 5, 99], [2, 5, 99], [3, 5]]
ziel  :  [[1, 5, 99], [2, 5, 99]]
quelle:  [[1, 5, 99], [2, 5, 99], [3, 5, 99]]
ziel  :  [[1, 5, 99], [2, 5, 99], [3, 5, 99]]

Re: Liste in Liste-Problem

Verfasst: Mittwoch 3. Februar 2021, 12:54
von __blackjack__
@NWA: Du steckst in der Schleife die *selben* Listen in `ziel` die auch in `quelle` sind. Und wenn Du die veränderst, dann ist diese Veränderung natürlich auf allen Wegen sichtbar auf denen diese Listen erreichbar ist. Python kopiert keine Daten wenn man das nicht explizit sagt. ``+`` bei Listen erzeugt beispielsweise eine (flache) Kopie der beiden Listen. Dann kann man das auch deutlich kompakter schreiben:

Code: Alles auswählen

#!/usr/bin/env python3


def main():
    quelle = [[1, 5], [2, 5], [3, 5]]

    ziel = [werte + [99] for werte in quelle]
    print("quelle:", quelle)
    print("  ziel:", ziel)


if __name__ == "__main__":
    main()

Re: Liste in Liste-Problem

Verfasst: Mittwoch 3. Februar 2021, 13:36
von NWA
Oh cool, dein code hat funktioniert. Ich hatte dein Lösung sogar auch im Rahmen der comprehensions gelesen.
Du steckst in der Schleife die *selben* Listen in `ziel` die auch in `quelle` sind.
Diesen Umstand hatte ich überprüft, ob sowas wie Referenzen verwendet wurden. Aber wenn man comprehensions hat, dann kopiert die for-Schleife doch Werte und keine Referenzen. Ich hatte das mit type überprüft.

PS: Und wie kann ich den Läufer aus der for-Schleife verwenden? Die Schleife scheint keine weiteren Anweisungen zu akzeptieren.

Re: Liste in Liste-Problem

Verfasst: Mittwoch 3. Februar 2021, 13:47
von __blackjack__
@NWA: Mit `type()` kann man das nicht überprüfen. Das gibt ja nur den Typ. Und einen Referenztyp als Datentyp in Python gibt es nicht. Und unter der Haube hat man *nur* Referenzen. Du kannst mit `id()` prüfen ob es sich um das selbe Objekt handelt oder zwei unterschiedliche.

Die ``for``-Schleife selbst oder eine comprehension im Allgemeinen kopieren nie Werte. In meinem Codebeispiel ist es wie gesagt das ``+`` auf Listen was dafür sorgt, dass eine neue Liste erstellt wird und nicht die Operanden verändert werden. Falls es `list.__add__()` nicht schon gäbe, könnte man das ungefähr so implementieren:

Code: Alles auswählen

    def __add__(self, other):
        result = list(self)
        result.extend(other)
        return result

Re: Liste in Liste-Problem

Verfasst: Mittwoch 3. Februar 2021, 14:36
von NWA
Vielen Dank für deine Hilfe!

Aber das append nur mit Referenzen arbeitet ist schon ein wenig enttäuschend. Vor allen Dingen findet sich dazu in der Literatur nichts.
Ich habe aber eine Alternative zu deiner Lösung gefunden. Ich habe append() gegen dein + ausgetauscht. Das sieht dann so aus:

Code: Alles auswählen

quelle = [[1,5],[2,5],[3,5]]

ziel = []

for innere_liste in quelle:
    ziel.append(innere_liste)
    ziel[len(ziel)-1] = ziel[len(ziel)-1] + [99] #Das +_Zeichen scheint die Lösung zu sein!
    print("quelle: ", quelle)
    print("ziel  : ", ziel)
Jetzt muss ich nur noch herausfinden, weshalb weitere append funktionieren, ohne die Quelle zu beeinflussen.

Code: Alles auswählen

quelle = [[1,5],[2,5],[3,5]]

ziel = []

for innere_liste in quelle:
    ziel.append(innere_liste)
    ziel[len(ziel)-1] = ziel[len(ziel)-1] + [99]
    ziel[len(ziel)-1].append(100)  #Das append fügt wieder ausschliesslich die 100 ins Ziel ein.
    print("quelle: ", quelle)
    print("ziel  : ", ziel)

Re: Liste in Liste-Problem

Verfasst: Mittwoch 3. Februar 2021, 15:00
von Sirius3
liste + liste gibt einfach eine neue Liste, und ändert keine Listen, wie das append oder expand macht.
Dein Code ist auch sehr umständlich geschrieben, da würde man gleich eine neue Liste an Ziel anhängen, statt erst die innere_liste und diesen Eintrag dann sofort wieder zu überschreiben:

Code: Alles auswählen

quelle = [[1,5],[2,5],[3,5]]

ziel = []
for innere_liste in quelle:
    ziel.append(innere_liste + [99])
    print("quelle: ", quelle)
    print("ziel  : ", ziel)

Re: Liste in Liste-Problem

Verfasst: Mittwoch 3. Februar 2021, 15:06
von peterpy
Hallo NWA ,
es geht auch indem Du auch eine neue innere Liste erzeugst.

Code: Alles auswählen

quelle = [[1,5],[2,5],[3,5]] #Vorgaben-Liste
ziel = [] #Datenliste zum Speichern der Daten aus der Vorgabenliste
for innere_liste in quelle:
    neue_innere_liste = []
    for wert in innere_liste:
        neue_innere_liste.append(wert)
    neue_innere_liste.append(99)    
    ziel.append(neue_innere_liste) #Element "speichern"
print("quelle: ", quelle)
print("ziel  : ", ziel)
Gruss Peter

Re: Liste in Liste-Problem

Verfasst: Mittwoch 3. Februar 2021, 15:25
von Sirius3
@peterpy: zum Kopieren einer Liste gibt es deutlich einfachere Konstrukte:

Code: Alles auswählen

neue_innere_liste = list(innere_liste)

Re: Liste in Liste-Problem

Verfasst: Mittwoch 3. Februar 2021, 16:26
von __blackjack__
@NWA: Was ist denn daran ”enttäuschend”? Ich möchte nicht die langen Gesichter sehen wenn das anders wäre, einmal wegen der Laufzeit und dem zusätzlichen Speicherverbrauch die das in 99,999% der Fälle unnötige kopieren zur Folge hätte und zum anderen weil: wie würdest Du das denn machen wenn Du keine Kopie anhängen möchtest aber `append()` immer eine Kopie anlegt?

Und Kopie: Bist Du dann als nächstes enttäuscht das es nur eine flache Kopie wäre oder würdest Du etwa sogar ein automatisches tiefes kopieren bei einem simplen `append()` haben wollen.

Was hättest Du denn in der Literatur erwartet? Da steht das `append()` ein Objekt an eine Liste anhängt. Und genau das macht es auch. Da steht nicht das eine Kopie angehängt wird. Wüsste auch nicht warum man das erwarten sollte, denn nichts an der Sprache sollte einen das erwarten lassen, weil Python selbst wie gesagt *nie* implizit Werte kopiert bei Zuweisungen oder Aufrufen. Man weist da immer das *selbe* Objekt zu und übergibt das *selbe* Objekt.

Python arbeitet auf der einen Seite *immer* mit Referenzen, auf der anderen Seite *nie* mit Referenzen. Denn einen Typ „Referenz“ gibt es in Python nicht. Alle Werte verhalten sich da gleich. Zuweisungen und Argumente bei Aufrufen (was ja eigentlich nur Zuweisungen sind — an lokale Namen) beziehen sich immer auf das gleiche Objekt. Das Aufrufmodell wird auch als „call by object sharing“ bezeichnen. Und das macht bei einer objektorientierten Programmiersprache auch total Sinn. Macht Java beispielsweise letztlich genau so. Wenn man da eine `ArrayList` als Element an eine andere `ArrayList` anhängt, dann wird da auch nicht auf magische Weise was kopiert. Bei anderen Sprachen ist das genau so. JavaScript beispielsweise. Alles total enttäuschende Sprachen? 🙂

Re: Liste in Liste-Problem

Verfasst: Mittwoch 3. Februar 2021, 21:46
von NWA
hast ja recht. ich war halt nur überrascht. mit hochsprachen habe ich es (noch) nicht so. aber nochmals danke für eure tipps.

Re: Liste in Liste-Problem

Verfasst: Samstag 6. Februar 2021, 21:44
von NWA
So, ich dachte ich hätte es verstanden. Aber anscheinend doch nicht...

Ich habe oben beschriebene Zeile in Einzelbefehle aufgeteilt:

Code: Alles auswählen

ziel.append(innere_liste + [99]) #Element "speichern"
Das sieht jetzt so aus:

Code: Alles auswählen

quelle = [[1,5],[2,5],[3,5]] #Vorgaben-Liste

ziel = [] #Datenliste zum Speichern der Daten aus der Vorgabenliste

for innere_liste in quelle:
    ziel += [innere_liste]
    ziel[len(ziel)-1] += [99]
#    ziel.append(innere_liste + [99]) #Element "speichern"
    print("quelle: ", quelle)
    print("ziel  : ", ziel)
Jedoch wird in dieser Schreibweise wieder die Quelle verändert:

Code: Alles auswählen

quelle:  [[1, 5, 99], [2, 5], [3, 5]]
ziel  :  [[1, 5, 99]]
quelle:  [[1, 5, 99], [2, 5, 99], [3, 5]]
ziel  :  [[1, 5, 99], [2, 5, 99]]
quelle:  [[1, 5, 99], [2, 5, 99], [3, 5, 99]]
ziel  :  [[1, 5, 99], [2, 5, 99], [3, 5, 99]]
Ich habe gedacht, dass append mit Referenzen und + mit Werten arbeitet...
Kann mir mal bitte jemand eine Seite zeigen, wo das supadupagenau erklärt wird, dann brauch ich nicht immer so doof fragen.

Re: Liste in Liste-Problem

Verfasst: Samstag 6. Februar 2021, 21:58
von __blackjack__
@NWA: Es gibt keinen Unterschied Referenz vs. Wert! Das wird immer alles gleich behandelt. Was Du hier geändert hast ist ``+`` und ``+=``. Das ist nicht das gleiche. Die Implementierung von ``+=`` kann/darf das Objekt auf linken Seite verändern, und genau das passiert bei Listen. ``+=`` bei Listen ist letztlich das gleiche wie ein Aufruf der `extend()`-Methode. Darum benutze ich persönlich auch diese lieber, weil dann deutlicher ist was passiert.

Re: Liste in Liste-Problem

Verfasst: Sonntag 7. Februar 2021, 12:36
von NWA
Ok, habe jetzt viele Antworten gefunden. Das Problem war doch ein Verständnisproblem mit den Referenzen. Für die Nachwelt, Stichworte: mutable, Seiteneffekte, slicing.

Code: Alles auswählen

quelle = [[1,5],[2,5],[3,5]] #Vorgaben-Liste

ziel = [] #Datenliste zum Speichern der Daten aus der Vorgabenliste

for innere_liste in quelle:
    ziel.append(innere_liste[:]) #RICHTIG, slicing kopiert innere_Liste.
#    ziel += [innere_liste] #FALSCH, referenziert nur innere_liste
    ziel[len(ziel)-1] += [99]
#    ziel.append(innere_liste + [99]) #GEHT, weil aus innere_liste + [99] eine NEUE Liste erstellt wird.
    print("quelle: ", quelle)
    print("ziel  : ", ziel)

Re: Liste in Liste-Problem

Verfasst: Sonntag 7. Februar 2021, 12:58
von __blackjack__
@NWA: Wobei ich die slicing-Syntax hier nicht verwenden würde. Wenn Du die Liste (flach) kopieren möchtest, dann verwende entweder ``list(innere_liste)`` oder ``innere_liste.copy()``. Beides ist IMHO verständlicher und man bekommt auch garantiert eine (flache) Kopie, egal was `innere_liste` für einen Typ hat. Slicing erstellt bei Numpy-Arrays beispielsweise nicht zwingend eine Kopie, die `copy()`-Methode dagegen schon. Wenn man `list()` aufruft, dann ist man sicher, dass das Ergebnis eine Liste ist.

Randbemerkung: ``ziel[len(ziel) - 1]`` würde man nicht schreiben, denn das ist das gleiche wie das einfachere ``ziel[-1]``.

Re: Liste in Liste-Problem

Verfasst: Sonntag 7. Februar 2021, 13:34
von NWA
Danke nochmal für die Tipps. Ich habe hier zwar das Handbuch von Ernesti, aber das slicing habe ich dort aus Kapitel 12 und copy() ist leider erst in Kapitel 39 (S. 1023)...

Re: Liste in Liste-Problem

Verfasst: Sonntag 7. Februar 2021, 14:13
von Sirius3
@NWA: nein, nein, copy ist hier eine Methode von list, das hat nichts mit dem copy-Modul zu tun. Ein Problem vieler Bücher ist es, dass sie zwar zeigen, was alles geht, aber nicht, was gut ist.

Re: Liste in Liste-Problem

Verfasst: Sonntag 7. Februar 2021, 14:41
von __blackjack__
Oder halt auch nicht alles zeigen was geht, denn ein Buch kann ja nicht *alle* Methoden und *jeden* Einsatzzweck erklären. Und es kann auch sein das das Buch älter ist oder bei der Aktualisierung vergessen wurde die `copy()`-Methode auf Listen hinzuzufügen. Die ist für Sequenzen ”erst” seit Python 3.3 vorhanden.

Re: Liste in Liste-Problem

Verfasst: Sonntag 7. Februar 2021, 15:04
von NWA
Natürlich kann ein Buch nicht alle Methoden beschreiben. Ich haber hier aber ein neues Buch ( Ernesti, Python 3, Das umfassende Handbuch von 2020), wo die Lösungen verteilt sind. Dort beginnt auf S. 159 der Abschnitt mit den Listen, wo gleich append() beschrieben wird. Mein Problem wird zwar nur ein paar Seiten weiter schon beschrieben, aber es ist schon ein anderer Abschnitt. Dort wird auch auf S. 169 die Lösung mit dem slicing erklärt. Jedoch wird auf S. 156 das slicing bei Strings (immutable) als problematisch beschrieben.
Die richtige Lösung mit copy() konnte ich erst nach eurem Hinweis suchen bzw. finden. Sie stand auf S. 1022 im Kapitel für insider-Wissen.

Re: Liste in Liste-Problem

Verfasst: Sonntag 7. Februar 2021, 16:01
von __blackjack__
@NWA: Wird da tatsächlich die `copy()`-Methode beschrieben oder nicht doch eher das `copy`-Modul aus der Standardbibliothek? Das ist deswegen „Insiderwissen“, weil man das nur sehr selten benötigt. Auf keinen Fall für eine einfache flache Kopie von einer Liste oder einem anderen Grunddatentyp. Das ist Kanonen auf Spatzen und ineffizienter als direkt den Datentyp nach einer Kopie zu fragen.

Edit: Laut Inhaltsverzeichnis auf der Webseite beim Verlag des Buches geht es in dem Kapitel um das `copy`-Modul. Also hier um das falsche Werkzeug.

Re: Liste in Liste-Problem

Verfasst: Sonntag 7. Februar 2021, 16:11
von NWA
Ja, es geht hier um das copy-Modul. Aber hier steht auch: "An dieser Stelle kommt das Modul copy ins Spiel, das dazu gedacht ist, echte Kopien einer Instanz zu erzeugen."
Es beschreibt zwar mein Problem, scheint aber etwas anders zu sein. Trotzdem kann die copy-Methode von Listen sowas ähnliches machen und copy() wurde bei Listen nicht erwähnt. Ich bin deshalb davon ausgangen, dass slicing das nonplusultra zum Kopieren sei- solange es um mutables geht.