Problem bei Label-Erstellung zur Laufzeit

Fragen zu Tkinter.
Antworten
Arcx
User
Beiträge: 2
Registriert: Dienstag 15. September 2015, 18:00

Hallo liebe Gemeinde,

ich habe da ein kleines Problem mit meinem Python-Programm. Zunächst kurz zur Erklärung was das Programm tut und wie es aufgebaut ist.

Per Telnet verbindet sich das Programm mit einem managed Switch, liest gewisse Parameter aus und soll diese in einer GUI darstellen. Dazu habe ich einen Thread geschrieben, in dem die ganzen Telnet-Aufrufe und Verarbeitung der XML-Files stattfindet. Am Ende des Threads werden Listen mit den gewünschten Parametern in eine Queue geschrieben. Diese Listen können unterschiedlich lang sein, weil z.B. neue Streams während der Laufzeit am Switch an- oder abgemeldet werden und somit mal 5 Streams, mal 8 Streams und mal 1 Stream in der Liste steht.

Diese Listen werden im workerThread in eine Queue gepackt. Über Methode periodCall, innerhalb des workerThreads, wird die Methode processIncoming innerhalb der GUI alle 500ms aufgerufen und geprüft ob es neue Nachrichten in der Queue gibt:

Code: Alles auswählen

def periodicCall(self):
         self.gui.processIncoming()

         if not self.running:
            import sys
            sys.exit(1)

         self.master.after(500, self.periodicCall)

Code: Alles auswählen

def processIncoming(self):
        while self.queue.qsize():
            try:
                portLst = self.queue.get(0)
                self.setPortLabels(self.master, portLst)
                self.queue.join()
            except Queue.Empty:
                pass
Wenn es neue Nachrichten in der Queue gibt, dann wird die Methode setPortLabels aufgerufen und ihr die portLst übergeben. In dieser Methode sollen nun eine Anzahl an Labels erzeugt werden, die der Länge der Liste entspricht:

Code: Alles auswählen

def setPortLabels(self, master, portLst):
        for i in range(len(portLst)):
            lblPortNmb = Label(master)
            lblPortNmb.config(text="Port " + str(portLst[i].portNumber))
            lblPortNmb.grid(row=0, column=i+1)
        self.queue.task_done()
Das funktioniert auch, allerdings wird, je länger das Programm läuft, der Speicherbedarf größer und die GUI immer weniger responsive. Nach 10 Minuten Laufzeit dauert ein button-klick schon mehrere Sekunden.

Ich vermute, das liegt daran, dass lblPortNmb immer wieder neu erzeugt wird und nicht mehr gelöscht wird. Ich hab es allerdings auch schon mit einer global definierten Liste probiert, sodass ich innerhalb der Methode setPortLabel per

Code: Alles auswählen

Liste.append(Label(master))
die Labels in die Liste geschrieben habe und am Ende der Methode per

Code: Alles auswählen

del Liste[:]
gelöscht hab. Das Ergebnis war jedoch das gleiche :(
Das einzige, was funktioniert, ist die Label statisch bei der Initialisierung zu erzeugen und in der Methode setPortLabels nur noch den Text zu ändern - dabei gibt es keine Probleme - ist jedoch nicht das, was ich brauche.

Ich hoffe sehr, dass mir jemand sagen kann was ich falsch mache und mir helfen kann.

Vielen Dank schonmal!
Sirius3
User
Beiträge: 17748
Registriert: Sonntag 21. Oktober 2012, 17:20

@Arcx: bei Tk reicht es nicht, das Objekt in Python zu löschen, Du mußt vorher das tk-Widget mit destroy entfernen.
Arcx
User
Beiträge: 2
Registriert: Dienstag 15. September 2015, 18:00

Sirius3 hat geschrieben:@Arcx: bei Tk reicht es nicht, das Objekt in Python zu löschen, Du mußt vorher das tk-Widget mit destroy entfernen.
Wow, super, das war es tatsächlich! Vieeelen dank, Sirius3!

Allerdings hat sich daraus ein weiteres Problem ergeben, das vorher nicht da war ^^ Sobald ich das Fenster "dragge" oder in der Größe ändere - und während ich den Mausbutton noch gedrückt habe die Labels zerstört werden, springt das Fenster zurück in seine ursprüngliche Position. Das ist erstmal nicht so schlimm - hauptsache das Programm läuft endlich einigermaßen sauber ;) - aber hast du dazu evtl. auch noch ne Idee?

Nochmals vieeeelen Dank! :)
BlackJack

@Arcx: Noch ein paar Worte zum gezeigten Quelltext:

Die Namenschreibweise richte sich nicht nach dem Style Guide for Python Code.

Importe stehen am Anfang des Moduls damit man die Abhängigkeiten sehen kann ohne den gesamten Quelltext durchgehen zu müssen.

Ein Rückgabecode eines Prozesses ungleich 0 bedeutet das etwas nicht nach Plan verlaufen ist. Ist der ``if not self.running:`` so etwas? Zumal das eine komische Stelle im Programm ist um es zu beenden und `sys.exit()` Code nach dem dem Aufruf von der Tk-Hauptschleife quasi überspringt.

Die Bedingung von der ``while``-Schleife in `processIncoming()` ist irgendwie redundant zu der Ausnahmebehandlung die nichts tut. Eine ”Endlosschleife” die durch die Ausnahme verlassen wird, hätte das selbe bewirkt, aber mit weniger Code/Komplexität und einer Ausnahmebehandlung die nicht komplett ignorierend ist, da sie dann wenigstens Einfluss auf den Programmfluss hat.

Statt 0 und 1 sollte man `False` und `True` verwenden wenn man Wahrheitswerte meint. Ein `get(0)` kann beim Leser eventuell andere Vorstellungen erwecken als ein `get(False)`, denn zumindest kann man letzteres nicht als Index misinterpretieren.

Achtung: Der `Queue.join()`-Aufruf ist ein Programmierfehler! Wenn da jemals mehr als ein Element in der Queue wartet, dann bleibt das Programm dort hängen! Falls Du nicht irgendwo tatsächlich warten musst das alles aus der Queue garantiert abgearbeitet ist bevor Du etwas bestimmtes tust, dann kannst Du Dir `task_done()` und `join()` sparen. Die beiden Methoden werden üblicherweise auch nicht im selben Thread aufgerufen. Das macht wenig Sinn. Und falls man die benutzt dann muss man sehr defensiv programmieren was den Aufruf von `task_done()` angeht, denn der muss dann wirklich in *jedem Fall* getätigt werden für jedes erfolgreiche `get()`, auch wenn die Verarbeitung der Daten schief läuft. Denn sonst hängt auf der anderen Seite das `join()` bis in alle Ewigkeit. Da so auf zwei Methoden zu verteilen ist schon sehr grenzwertig. Man würde eher gleich nach dem `get()` einen ``try``-Block starten der die Verarbeitung umfasst und in einem ``finally`` dann diese Verarbeitung mit `task_done()` ”quittieren”. So ist sichergestellt dass das auch in Fällen passiert wo die Verarbeitung eine Ausnahme auslöst mit der man nicht gerechnet hat.

Konkrete Grunddatentypen, und dann auch noch abgkrzt gehören nicht in Namen. Es passiert häufiger das man im Laufe der Entwicklung den Datentyp mal ändert, zum Beispiel in einen anderen Grunddatentyp oder in einen eigenen Containertyp mit Funktionalität die der Grunddatentyp nicht hat, und dann hat man entweder falsche, irreführende Namen im Programm oder man muss den Namen überall ändern. Für Sequenztypen und iterierbare Objekte verwendet man in der Regel einfach die Mehrzahl des Begriffs der ein einzelnes Element beschreibt. In diesem Fall also `ports` statt `portLst`.

Muss beim Aufruf von `setPortLabels()` `master` als Argument übergeben werden? Die Methode hätte ja auch so schon Zugriff auf diesen Wert‽

Das mit dem ``for i in range(len(portLst)):`` ist in Python ein „anti pattern“. Man direkt über die Elemente von Listen (Sequenztypen im allgemeinen) iterieren ohne den unnötigen Umweg über einen Index. Wenn man *zusätzlich* einen Index benötigt, oder hier die Spaltennummer, dann gibt es die `enumerate()`-Funktion.

`lblPortNmb` ist wdr so ein Nme bei dm sich mir die Ncknhaare aufstlln. Das ist megagruselig und nicht wirklich gut lesbar.

Der `config()`-Aufruf ist überflüssig weil man die Beschriftung auch gleich bei der Erstellung des `Label`-Exemplars hätte angeben können.

`str()` und ``+`` um Werte und Zeichenketten zusammen zu bringen ist eher BASIC als Python. In Python gibt es dafür Zeichenkettenformatierung.

Das `Label` da direkt verwendet wird, lässt einen Sternchen-Import vermuten. Sollte dem so sein: Weg damit. Gerade bei Tkinter holt man sich damit eine ganze Menge Namen ins Modul von dem dann kaum etwas tatsächlich benutzt wird, aber es ”müllt” den Namensraum zu und es besteht die Gefahr von Namenskollisionen.

Den Inhalt von Listen mittels ``del liste[:]`` zu löschen ist eher unüblich. Man erstellt einfach ein neues, leeres Listenobjekt.

Die Methode würde man in Python also eher so schreiben:

Code: Alles auswählen

    def set_port_labels(self, ports):
        for label in self.port_labels:
            label.destroy()
        self.port_labels = list()
        for column, port in enumerate(ports, 1):
            label = tk.Label(
                self.master, text='Port {0.port_number}'.format(port)
            )
            label.grid(row=0, column=column)
            self.port_labels.append(label)
Antworten