Seite 1 von 1

Liste in Liste wird nicht übernommen

Verfasst: Dienstag 26. Oktober 2021, 06:35
von MrFox
Hallo Zusammen,

ich habe ein echtes Verständnisproblem welches mich so langsam wirklich ratlos lässt. Als Python Neuling kann es natürlich sein, dass ich einen Fehler mache. Ursprünglich komme ich aus der C Welt wo es doch oft deutlich strenger zugeht. :-)

Zu meinem Thema. Ich habe eine externe xml-Datei, welche ich Zeile für Zeile parse um eine Menüstruktur in einer Liste abzulegen. Das Menü ist in zwei Ebenen aufgeteilt. Also in Etwa:

Menu1
Menu1.1
Menü1.2
....
Menu2
Menu2.1
...
usw.

Hierfür möchte ich erst einmal eine Liste mit den Hauptmenüs plus deren Inhalt haben. Also wie folgt:
Liste 1 = Menu1 plus Untermenüs
Liste 2 = Menu2 plus Untermenüs
...
Am Ende dann eine Liste mit allen Menüs: [[Menu1], [Menu2]...]

Idee:
Ein Menu wird gestartet mit dem string <treeitem>
beendet wird es mit dem string </menuitem>

Ich bestimme also die Menü-Tiefe und wenn diese >0 ist bin ich einem Untermenü bzw. dessen Untermenüs. Solange sammle ich alle Zeilen mit der Liste "sub_menu_list" ein. Sobald das Level 0 erreicht bin ich wieder bei der Hauptstruktur und hänge alle gesammelten Zeilen (also die Liste "sub_menu_list") der Liste menu_list an. Dann lösche ich den Inhalt von sub_menu_list und weiter geht es.

Das Erstaunliche:
Die Liste sub_menu_list wird stets korrekt gefüllt. Ich prüfe dies direkt vor der Zeile menu_list.append. D.h. der Inhalt welchen ich anhängen möchte ist korrekt und vorhanden. Sehe ich mir allerdings die menu_list vor dem Löschen der sub_menu_list an dann fehlt der erste Eintrag. Sehe ich mir die menu_list NACH dem Löschen der sub_menu_list an fehlen alle Einträge. Das Ergebnis ist dann für die 17 Einsprünge in die elif section:
[ [ ] ]
[ [ ], [ ] ]
[ [ ], [ ], [ ] ]
.....
[ [ ], [ ], [ ], [ ], [ ], [ ], [ ], [ ], [ ], [ ], [ ], [ ], [ ], [ ], [ ], [ ], [ ] ]

Code: Alles auswählen

for line in input_file:
	if '<treeitem' in line:     
		tree_level += 1
	elif '</treeitem>' in line:
 		tree_level -= 1
	else:
		pass

	if tree_level > 0:
		sub_menu_list.append(line)
	elif tree_level == 0 and len(sub_menu_list) > 0:
		print(sub_menu_list) # at this point sub_menu_list is filled with the correct data!
		menu_list.append(sub_menu_list)
		test_counter += 1
		print(test_counter) # number of loops fit to the menu structure --> correct
		print(sub_menu_list) # this is always correct, even at the first run
		print(menu_list) # first strange behavior: the very first element is empty. The first sub_menu_list hasn't been appended but all the others
		while len(sub_menu_list) > 0:
			del sub_menu_list[0]
		print(menu_list)  # at this point menu_list is filled with 17 * []. The number of [] is correct as we have 17 main menus. But why are they empty as I only append filled lists to this list???
	else:
		pass	

Um ehrlich zu sein stehe ich der Verzweiflung nahe denn ich kann schon Mal überhaupt gar nicht verstehen wie es möglich ist dass .append stets mit korrektem Inhalt ausgeführt wird und dann doch zu einer leeren Liste führt. auch verstehe ich überhaupt nicht wie es möglich ist dass das Löschen der Liste sub_menu_list überhaupt einen Einfluss nehmen kann auf ein vollkommen anderes Objekt, nämlich menu_list. Dort sind ja bereits Inhalte vorhanden. Warum ist die Menü Liste VOR dem Löschen der Sub MEnü Liste noch gefüllt während sie direkt nach dem Löschen wieder leer ist?
Bin um Hilfe dankbar. :-)

Re: Liste in Liste wird nicht übernommen

Verfasst: Dienstag 26. Oktober 2021, 08:49
von /me
Du fügst mit `menu_list.append(sub_menu_list)` sub_menu_list zu menu_list hinzu. Und es ist wirklich das Objekt auf das sub_menu_list referenziert was du hinzufügst, nicht eine Kopie davon. Später gehst du her und löscht in einer while-Schleife alle Einträge aus sub_menu_list.

Da dieses Objekt eben nicht ein vollkommen anderes ist als dasjenige das du der Liste menu_list hinzugefügt hast, sondern exakt das gleiche Objekt, spiegeln sich diese Änderungen natürlich auch in menu_list wider.

Re: Liste in Liste wird nicht übernommen

Verfasst: Dienstag 26. Oktober 2021, 09:48
von __blackjack__
@MrFox: XML kann man nicht zeilenweise verarbeiten, weil die Struktur durch die XML-Auszeichnungen beschrieben wird, nicht durch Zeilen. XML verarbeitet man mit einem entsprechenden Parser. Zum Beispiel mit der ElementTree-API aus der Standardbibliothek.

Die ``else: pass``-Zweige machen keinen Sinn. Wenn Du da nix machen willst, dann lass sie einfach weg.

Du löschst den Inhalt von `sub_menu_list` auf die denkbar ungünstigste Weise. Das erste Element entfernen bedeutet das alle Folgeelemente um eine Position nach vorne rücken müssen. Wenn man so etwas denn machen würde, dann haben Listen auch eine `clear()`-Methode, oder man löscht mit *einem* ``del liste[:]`` gleich alles.

Aber löschen einzelner Elemente aus Listen oder gar alle, macht man nur ganz selten. Man baut normalerweise eine neue Liste auf, ohne die Elemente die man nicht mehr haben will. In diesem Fall also einfach eine neue, leere Liste: ``[]``.

Der Name von Grunddatentypen sollte nicht in Namen vorkommen. Man ändert so etwas gerne mal während man entwickelt, und dann hat man entweder falsche, weil irreführende Namen, oder man muss überall alle betroffenen Namen anpassen.

Wenn Du von C kommst, dann stell Dir jedes Objekt unter der Haube als einen Zeiger auf ein `PyObject`-``struct`` vor. Denn genau das passiert da. Du hast immer die *selbe* Liste hinzugefügt.

Code: Alles auswählen

In [287]: xs = []                                                               

In [288]: ys = []                                                               

In [289]: id(xs), id(ys)                                                        
Out[289]: (140709312221832, 140709312227400)

In [290]: xs.append(ys)                                                         

In [291]: xs.append(ys)                                                         

In [292]: xs                                                                    
Out[292]: [[], []]

In [293]: id(xs[0]), id(xs[1])                                                  
Out[293]: (140709312227400, 140709312227400)

In [294]: ys.append(42)                                                         

In [295]: ys                                                                    
Out[295]: [42]

In [296]: xs                                                                    
Out[296]: [[42], [42]]

In [297]: ys.clear()                                                            

In [298]: ys                                                                    
Out[298]: []

In [299]: xs                                                                    
Out[299]: [[], []]

Re: Liste in Liste wird nicht übernommen

Verfasst: Sonntag 31. Oktober 2021, 12:03
von __SmallBlind
Ich grüße,

Um mich auf das geschilderte Problem zu beziehen kannst du wie @/me bereits beschrieben hat eine Kopie übergeben. Das machst du mit einem angefügten .copy() oder sollten sich in sub_menu_list auch Listen befinden mit deepcopy(sub_menu_list). Das liegt daran, dass die Übergabe von Listen in Python mit callbyreference funktioniert, du aber von callbyvalue ausgehst.

Ich hoffe, ich konnte einen Teil der Verzweiflung beenden.

Re: Liste in Liste wird nicht übernommen

Verfasst: Sonntag 31. Oktober 2021, 16:04
von __blackjack__
Das ist weder call by reference noch call by value, sondern call by object sharing. 🙂

Re: Liste in Liste wird nicht übernommen

Verfasst: Sonntag 31. Oktober 2021, 20:18
von __SmallBlind
call by object sharing
Du hast natürlich Recht, jedoch gilt bei call by sharing für nicht primitive Datentypen, dass diese wie bei call by reference übergeben werden. Das muss man, denke ich, wissen um das Problem, welches der Autor schildert, zu lösen. Vielen Dank jedoch für die Berichtigung. Gerade wenn man aus der C-Ecke kommt ist das call by sharing Prinzip für den Einstieg wichtig zu wissen und da hätte mein Beitrag verwirren können.

Re: Liste in Liste wird nicht übernommen

Verfasst: Sonntag 31. Oktober 2021, 20:48
von __deets__
Ich würde es ja eher anders formulieren: es gibt keine primitiven Typen (erst recht nicht seitdem Python automatisch eine Bereichserweiterung macht, oder auf Fliesskomma-Zahlen wechselt, außer man verlangt explizit mit // es lässt das). Aber viele Typen (zahlen, aber vor allem auch strings) sind einfach immutable. Und damit spielt das dann keine Rolle mehr. Die unterteilen primitiv / komplexe Typen war oder ist bei Java zb relevant.

Re: Liste in Liste wird nicht übernommen

Verfasst: Sonntag 31. Oktober 2021, 21:50
von /me
__SmallBlind hat geschrieben: Sonntag 31. Oktober 2021, 20:18 Du hast natürlich Recht, jedoch gilt bei call by sharing für nicht primitive Datentypen, dass diese wie bei call by reference übergeben werden.
Was ist für dich ein primitiver Datentyp? In Python ist alles ein Objekt.

Falls du Strings und Zahlen für primitive Datentypen hältst, dann bin ich anderer Meinung. Diese Typen sind zwar unveränderlich (immutable), aber keinesfalls primitiv. Unter der Haube werden prinzipiell alle Datentypen bei Zuweisungen oder Übergabe als Parameter gleich behandelt. Gewisse interne Optimierungen unterschiedlicher Python-Implementierungen sind dabei nicht von Belang.

Re: Liste in Liste wird nicht übernommen

Verfasst: Sonntag 31. Oktober 2021, 22:10
von kbr
Da in Python alles ein Objekt ist, sind Argumentübergaben immer "call by reference", egal wie dies auch anders benannt werden könnte. Das Verhalten des Objektes hängt vom Typ ab.

Re: Liste in Liste wird nicht übernommen

Verfasst: Montag 1. November 2021, 00:45
von __blackjack__
@kbr: Das ist nicht call by reference. Call by reference würde bedeuten:

Code: Alles auswählen

def f(x):
    x = 23

def g(items):
    items = [1, 2, 3]

def main():
    a = 42
    f(a)
    print(a)  # 23
    elements = [4, 7, 1, 1]
    g(elements)
    print(elements)  # [1, 2, 3]
Eine Sprache in der es nur call by reference gibt, wäre beispielsweise QBasic und da würde das so wie in den Kommentaren funktionieren. Also zumindest der erste Teil, weil man in QBasic Arrays keinen Wert zuweisen kann. In Pascal gibt es beides, call by value und call by reference. Und in C gibt es nur call by value. Um mal Beispiele für alle Kombinationen mit call by reference/value zu geben, an denen man sich orientieren kann.

Re: Liste in Liste wird nicht übernommen

Verfasst: Montag 1. November 2021, 12:49
von kbr
@__blackjack__: Ich finde der Begriff "call by object sharing" steht eher näher zu "call by future" oder "call by need", die mehr ein Verhalten beschreiben. Weniger aber die technische Art, wie Parameter übergeben werden. Und dies sind bei Python Referenzen.

Anschließend wird es interessant, wenn unterschiedliche Eigenschaften von derart referenzierten Objekten (wie z.B. mutable, immutable oder callable) zum Tragen kommen, oder auch das Verhalten, das an Namen gebundene Referenzen ausgetauscht werden können. Also das in deinem Beispiel

Code: Alles auswählen

def g(items):
    items = [1, 2, 3]

die im lokalen Namensraum an `item` gebundene Referenz durch die Referenz des neuen Objektes ersetzt wird und somit keinen Seiteneffekt auslöst.

Und das immutable Objekte mit gleichem Wert, egal wie oft sie, und von wo aus sie referenziert werden, möglicherweise nur einmal existieren (beispielsweise die numerischen Objekte in obiger Liste). Insofern geht "object sharing" in Python weit über die Parameter-Übergabe hinaus.

Re: Liste in Liste wird nicht übernommen

Verfasst: Montag 1. November 2021, 15:59
von __blackjack__
@kbr: Eben darum geht es doch aber, um die Semantik. Es ist doch wie immer bei diesen Fragen wo jemand verwirrt ist, die Frage wie sich das verhält, und nicht wie es implementiert ist. Denn für letzteres müsste man dann ja auch noch C kennen oder lernen.

Und genau das dieses neu zuweisen in der Funktion keinen Einfluss auf die Bindung ausserhalb hat, ist für mich der Grund, dass das *nicht* call by reference ist. Denn in den Sprachen die call by reference haben und das auch so nennen, passiert nämlich genau das. Sei das C++, Pascal, oder QBasic. Wenn man jetzt behauptet das in Python sei call by reference, denken C++-, Pascal-, und QBasic-Programmierer aha, ich weiss jetzt wie das funktioniert, nur um dann doch wieder verwirrt zu sein wenn es sich anders verhält. Das Verhalten von Python passt nicht in das call by reference/value Muster das man aus anderen Sprachen kennt, und deshalb ist es IMHO sinnvoll und wichtig das auch nicht so zu nennen. Es stiftet nur unnötig Verwirrung.

Die Deutsche Wikipedia schreibt zu call by reference das beim Aufgerufenen eine Zuweisung möglich ist, die der Aufrufer dann auch sieht, und die englische Wikipedia hat unter anderem den Satz „A simple litmus test for whether a language supports call-by-reference semantics is if it's possible to write a traditional swap(a, b) function in the language.“ Und das geht in Python nicht.

Re: Liste in Liste wird nicht übernommen

Verfasst: Montag 1. November 2021, 22:42
von kbr
@__blackjack__: Den Lackmus Test hatte ich auch wahrgenommen und Python besteht diesen tatsächlich nicht. Ich weiß aber nicht, wie weit man z.B. C++ Programmierern entgegen kommen sollte, um Python verständlich darzulegen. Ein "call by object sharing" ist dort unbekannt und dann läuft es doch wieder auf das Erklären von Details hinaus. Wo holt man C++ Programmierer gedanklich am besten ab? Wenn es nicht "call by reference" sein soll, dann vielleicht mit "call by reference value", was es möglicherweise am besten trifft: denn es werden Werte übergeben, die als Referenzen dienen.

Ein "call by object sharing" ist erst einmal unverständlich. Insofern holt man damit niemanden ab, der es nicht schon kennt. Und spätestens bei einer Zuweisung wie `items = [1, 2, 3]` ist es mit dem (aus der C++ Welt möglicherweise antizipierten) sharing auch schon wieder vorbei – was auch verwirrend wirken kann.

D.h. ich sehe dein Argument, halte die Bezeichnung "call by object sharing" aber dennoch für unglücklich, auch wenn diese in das Fachvokabular eingeflossen ist. Diese Bezeichnung mag zur akademischen Differenzierung beitragen, ist aus meiner Sicht aber nicht dazu geeignet, das Verständnis des darunter liegenden Mechanismus zu erleichtern. Der Umstand, dass Python mit Namen und Referenzen arbeitet, dem User aber nicht den Datentyp Pointer bereitstellt, bleib auf jeden Fall erklärungsbedürftig.

Re: Liste in Liste wird nicht übernommen

Verfasst: Montag 1. November 2021, 23:25
von __blackjack__
@kbr: Aber gerade weil der C++-Programmierer damit nichts anfangen kann ist das doch gut. Dann kommt er nicht in Versuchung einen ihm bekannten Begriff zu verwenden und zu glauben das funktioniert so in Python, nur um dann irgendwann doch wieder auf die Nase zu fallen, weil es halt nicht stimmt.

Wobei der OP von C kommt. Da kann man dann ja einfach sagen, dass in der Implementierung von CPython jedes Objekt durch ein einen Zeiger auf ein PyObject-``struct`` repräsentiert wird, und das man an den Zeiger selbst in Python nicht heran kommt, sondern immer nur an das Objekt auf das er verweist.

Re: Liste in Liste wird nicht übernommen

Verfasst: Dienstag 2. November 2021, 09:30
von kbr
@__blackjack__: Ich hole Leute gerne bei Dingen ab, die sie bereits kennen. Gerade wenn jemand von C kommt, klappt das mit dem von dir erwähnten `struct` ganz gut.