tkinter - Frage bzgl. Variable in Buttons

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Kahnbein.Kai
User
Beiträge: 104
Registriert: Mittwoch 24. Juni 2015, 14:12
Wohnort: Bochum

Guten Morgen,
ich muss leider nochmal nerven und das Forum in Anspruch nehmen. Ich habe meinen tkinter Programm erweitert/umgeschrieben und weiß leider nicht genau warum es funktioniert, bzw. kann mir nicht erklären wo die betreffende 'self.button_count' Variable gespeichert wird.

Ablauf:
Wird das Programm gestartet erscheint nur ein Button mit dem Text "Add Button" klickt man auf diesen Button werden beliebig viele neue Buttons hinzugefügt. Deren Bezeichnung entspricht deren Anzahl, beginnend bei Null. Die Variabel 'self.button_count' wird bei jedem hinzufügen eines Button um eins erhöht, zusätzlich steuert diese Variable auch die Platzierung bzw. die row-Nummer. Jeder neu hinzugefügte Button wird in die Liste 'button_list' geschrieben (Dank sirius3)

Klickt man auf einem hinzugefügten Button wird eine lambda Funktion ausgelöst, diese Übergibt die 'self.button_count' Variable des angeklickten Buttons an die destroy_button Funktion. Zuerst wird eine Ausgabe mit den zerstörten Button ausgegeben und im Anschluss der Button zerstört.

Frage:
Warum funktioniert das so, bzw. wo wird pro Button die Variabel 'self.button_count' gespeichert. Wenn man z. B. fünf Buttons erzeugt, entspricht der Variablenwert am Schluss fünf also der maximalen Anzahl an hinzugefügten Buttons. Ich würde erwarten, dass jeder Button dann den Wert fünf ausgibt und nicht seine "eigene" Nummer, da der vorherige Wert immer überschrieben wird.

Ich habe versucht mich an alle Regeln bzgl. Variablennamen und Leerzeilen zu halten, ich hoffe es hat geklappt :).

Code: Alles auswählen

import tkinter as tk

class Programm(tk.Tk):
    
    def __init__(self):
        super().__init__()
        self.add_button = tk.Button(self, text="Add Button", command=self.button_add) # hinzufüge Button
        self.add_button.grid(row=0, column=0) # "hinzufüge Button" in die Oberfläche platzieren
        self.button_count = 0 # zählt jeden hinzugefügten Button und bestimmt deren Platzierung
        self.button_list = [] # Liste mit den hinzugefügten Buttons um darauf zugreifen zu können


    def button_add(self): # Funktion um beliebig viele Buttons hinzuzufügen
        button = tk.Button(self, text="Number " + str(self.button_count), # neuer Button mit seiner row-Nummer als Text
                                command=lambda button_number = self.button_count : self.destroy_button(button_number)) 
        # die lambda Funktion übergibt die Variable button_count and die destry_button funktion
        button.grid(row=self.button_count+1, column=0)
        self.button_count += 1
        self.button_list.append(button)


    def destroy_button(self,button_number): # zerstörungsfunktion anhand von self.button_count und Liste der hinzugefügten Buttons
        print("Button " + str(button_number) + " is destroyed")
        self.button_list[button_number].destroy()


def main():
    root = Programm()
    root.mainloop()
    
if __name__ == "__main__":
    main()

Gruß Kai
Benutzeravatar
Dennis89
User
Beiträge: 1226
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

beim erstellen des Buttons gibst du in der lambda-Funktion "button_number" den Wert von "self.button_count" zum Zeitpunkt des erstellens. Und "button_number" wird dann als Argument für "destroy_button" angegeben.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Sirius3
User
Beiträge: 17844
Registriert: Sonntag 21. Oktober 2012, 17:20

Du hättest gerne den Thread viewtopic.php?p=423761 fortsetzen können, statt eines neuen aufzumachen.

statt Strings mit + zusammenzustückeln benutzt man Formatstrings.
Statt lambda-Funktionen benutzt man functools.partial.
Da button_count immer die Länge von button_list ist, kann sich dieses Attribut auch sparen.
Statt button_list ist es üblich die Variable einfach buttons zu nennen.
Kommentare sollten dem Leser einen Mehrwert bieten, die meisten Kommentare beschreiben aber nur exakt das, was eh schon dasteht.
Der Kommentar bei button_add ist zudem mehrdeutig, weil die Funktion exakt einen Button hinzufügt und nicht beliebig viele.
Solche Kommentare kann man gut als Doc-String schreiben.
Der Kommentar bei destroy_button schreibt was von button_count, obwohl das gar nicht benutzt wird.

Code: Alles auswählen

import tkinter as tk
from functools import partial

class Programm(tk.Tk):
    def __init__(self):
        super().__init__()
        self.add_button = tk.Button(self, text="Add Button", command=self.button_add)
        self.add_button.grid(row=0, column=0)
        # Liste mit den hinzugefügten Buttons, um darauf zugreifen zu können
        self.buttons = []


    def button_add(self):
        """ Funktion um einen weiteren Button hinzuzufügen """
        button_count = len(self.buttons)
        button = tk.Button(self,
            text=f"Number {button_count}",
            command=partial(self.destroy_button, button_count)
        button.grid(row=button_count + 1, column=0)
        self.buttons.append(button)


    def destroy_button(self, button_number):
        """ Zerstörungsfunktion, um den Button mit der Nummer button_number zu löschen """
        print(f"Button {button_number} is destroyed")
        self.buttons[button_number].destroy()


def main():
    root = Programm()
    root.mainloop()
    
if __name__ == "__main__":
    main()
Sowohl lambda als auch partial erzeugen ein neues Objekt, das die (Default-)Argumente (hier button_count) speichert.
Die zerstörten Buttons existieren weiterhin, als Eintrag in der Liste, und auch das Grid besteht weiterhin aus der gleichen Anzahl an Reihen, nur dass dann eine Reihe keinen Inhalt mehr hat.
Benutzeravatar
__blackjack__
User
Beiträge: 13268
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wegen der leeren Gridzellen frage ich mich ob es denn unbedingt `grid()` sein muss. Momentan sieht man da ja nix warum man das nicht einfach mit `pack()` lösen kann. Und in beiden Fällen in einen eigenen Frame, denn dieses ``+ 1`` was im anderen Thema noch ein ``+ …``, was anderes halt, war, ist schon ein bisschen unübersichtlich. Insbesondere wenn da noch mehr Code dazu kommt, kann man dann gerne mal vergessen, dass es das noch gibt, und das man das anpassen muss wenn man was vor/über den Buttons im Grid ändert.
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
Kahnbein.Kai
User
Beiträge: 104
Registriert: Mittwoch 24. Juni 2015, 14:12
Wohnort: Bochum

Vielen Dank für die Antworten. Ich habe noch ein paar Verständnisfragen:
Sirius3 hat geschrieben: Montag 9. Oktober 2023, 08:03 Du hättest gerne den Thread viewtopic.php?p=423761 fortsetzen können, statt eines neuen aufzumachen.
Alles klar, mache ich beim nächsten mal.
Sirius3 hat geschrieben: Montag 9. Oktober 2023, 08:03 Sowohl lambda als auch partial erzeugen ein neues Objekt, das die (Default-)Argumente (hier button_count) speichert.
Die zerstörten Buttons existieren weiterhin, als Eintrag in der Liste, und auch das Grid besteht weiterhin aus der gleichen Anzahl an Reihen, nur dass dann eine Reihe keinen Inhalt mehr hat.
Ok, versteh ich das richtig ? Durch einen klick auf "Add Button" erzeuge ich ein Objekt der Art tk Button und ein lambda bzw. partial Objekt ?
Mit dem zerstören des Buttons wird dann auch das jeweilige lambda bzw. partial Objekt zerstört ?

Kann ich auf die Defaultargumente der lambda bzw. partial objekte zugreifen ?
__blackjack__ hat geschrieben: Montag 9. Oktober 2023, 09:41 Wegen der leeren Gridzellen frage ich mich ob es denn unbedingt `grid()` sein muss.
Nein, muss es nicht, ich finde es nur schön übersichtlich mit Zeilen und Spalten :)

Gruß und vielen Dank Kai
Benutzeravatar
__blackjack__
User
Beiträge: 13268
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Kahnbein.Kai: Ob das *Python*-Objekt das als `command` übergeben wird, von einem `destroy()` betroffen ist, bezweifle ich. Aber ist das wichtig?

Was meinst Du mit Default-Argumenten von ``lambda``/`partial`-Objekten?

Mal davon abgesehen das die Möglichkeiten von Grid hier gar nicht genutzt werden, ist das halt doof das da leere Zellen entstehen wenn man Elemente zerstört.
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
Kahnbein.Kai
User
Beiträge: 104
Registriert: Mittwoch 24. Juni 2015, 14:12
Wohnort: Bochum

__blackjack__ hat geschrieben: Montag 9. Oktober 2023, 20:11 @Kahnbein.Kai: Ob das *Python*-Objekt das als `command` übergeben wird, von einem `destroy()` betroffen ist, bezweifle ich. Aber ist das wichtig?
Nein, ist es nicht. Ich versuche das nur zu verstehen, daher meine Frage. Mir war nicht klar das lambda und partial ein neues Objekt erzeugen.

edit: Jetzt habe ich es auch in der Doku gefunden. Unter dem Suchbegriff "Lambda" findet man nur ein Glossary Eintrag. Der richtige Bergriff ist "Lambdas" ....


__blackjack__ hat geschrieben: Montag 9. Oktober 2023, 20:11 Was meinst Du mit Default-Argumenten von ``lambda``/`partial`-Objekten?
Daher :)
Sirius3 hat geschrieben: Montag 9. Oktober 2023, 08:03 Sowohl lambda als auch partial erzeugen ein neues Objekt, das die (Default-)Argumente (hier button_count) speichert.
Ich versuche es mal mit .pack()

Gruß Kai
Kahnbein.Kai
User
Beiträge: 104
Registriert: Mittwoch 24. Juni 2015, 14:12
Wohnort: Bochum

Irgendwie kann ich meinen vorherigen Beitrag nicht mehr Editieren....
Ich habe nochmal ein wenig rumprobiert.

Code: Alles auswählen

for i in range(5):
    lambda_test = lambda x : x * 100
    print(lambda_test(i))
    print(lambda_test)
Wenn ich den Code ausführe bekomme ich diese Ausgabe:

Code: Alles auswählen

runfile('/home/kai/Dokumente/Python/Interface/untitled0.py', wdir='/home/kai/Dokumente/Python/Interface')
0
<function <lambda> at 0x7fe7d444a8b0>
100
<function <lambda> at 0x7fe7d444aca0>
200
<function <lambda> at 0x7fe7d444aa60>
300
<function <lambda> at 0x7fe7d444ac10>
400
<function <lambda> at 0x7fe7d444a040>
Dann sind das die Objekte (Funktionen) der Lambdafunktion und deren Speicherorte oder ?

Gruß Kai
Sirius3
User
Beiträge: 17844
Registriert: Sonntag 21. Oktober 2012, 17:20

Genau, in Deiner for-Schleife erzeugst Du immer wieder neue Lambda-Objekte.
Benutzeravatar
__blackjack__
User
Beiträge: 13268
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wobei die aber auch automagisch von der Speicherbereinigung wieder abgeräumt werden. Das die alle unterschiedliche Adressen haben bedeutet nicht, dass die zwangsläufig auch gleichzeitig existieren müssen. Und das die ID eines Objekts in CPython die Adresse ist, an der das Objekt im Speicher liegt, ist ein Implementierungsdetail. In Jython sieht die Ausgabe so aus:

Code: Alles auswählen

0
<function <lambda> at 0x2>
100
<function <lambda> at 0x3>
200
<function <lambda> at 0x4>
300
<function <lambda> at 0x5>
400
<function <lambda> at 0x6>
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
Kahnbein.Kai
User
Beiträge: 104
Registriert: Mittwoch 24. Juni 2015, 14:12
Wohnort: Bochum

Ah ok, vielen Dank für die Erklärungen.

Eine letzte Verständnisfrage habe ich jedoch noch, dort wird aber nur die Funktion gespeichert oder auch der entsprechende Rückgabewert ?

Gruß Kai
Benutzeravatar
sparrow
User
Beiträge: 4244
Registriert: Freitag 17. April 2009, 10:28

Wo ist denn "dort"?
Kahnbein.Kai
User
Beiträge: 104
Registriert: Mittwoch 24. Juni 2015, 14:12
Wohnort: Bochum

In der Adresse im Speicher.
__blackjack__ hat geschrieben: Dienstag 10. Oktober 2023, 12:40

Code: Alles auswählen

0
<function <lambda> at 0x2>
100
<function <lambda> at 0x3>
200
<function <lambda> at 0x4>
300
<function <lambda> at 0x5>
400
<function <lambda> at 0x6>
Ich bekomme den Rückgabewert aber weder mit dir bzw. dict ausgelesen, daher denke ich dort ist nur die Funktion gespeichert.

Gruß Kai
Benutzeravatar
sparrow
User
Beiträge: 4244
Registriert: Freitag 17. April 2009, 10:28

Dann guck dir mal dein Cofebeidpiel noch einmal an, dann solltest du dir die Frage selbst beantworten können.

Du hast ja zwei verschiedene Zeilen, die unterschiedliche Ausgaben verursachen.
Antworten