Button auslesen

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.
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@emmamenden: hier geht es auch um Lesbarkeit und Optik. Dem Compiler ist es egal, dass Du wie Kraut und Rüben eingerückt hast. Dem sind auch die komischen Zeilen mit den vielen Minuszeichen egal, mich behindern sie beim Lesen. Üblich sind zwei Leerzeilen zwischen Funktionen um einen optischen Abstand zu haben.

Näher habe ich mir Deinen Code auch nicht angeschaut, wenn mit `global` und Threads es wenig Sinn macht, die ganzen anderen Baustellen anzugehen.
Falsch ist zum Beispiel noch die Funktion zeige_matrix, weil Du darin ständig neue Buttons erzeugst, statt die bestehenden zu ändern. GUI-Elemente werden einmal beim Erzeugen des Fensters angelegt und danach nur noch verändert.
Warum rechnest Du Dir einen Tastenwert aus Spalte und Zeile aus, nur um den später wieder in Zeile und Spalte zurückzurechnen?
Der Tastenname wird gar nicht benutzt.
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Grundsätzlich vielleicht mal ein Hinweis auf den Style Guide for Python Code. Das mit der Namensschreibweise (klein_mit_unterstrichen statt camelCase) könnte man auch einfach umsetzen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
Dennis89
User
Beiträge: 1123
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

hier habe ich mal ein kleines Beispiel, wie du so ein GUI aufbauen solltest. Arbeite dich in die objektorientierte Programmierung ein und berücksichtige die Ratschläge die dir gegeben wurden.

Code: Alles auswählen

#!/usr/bin/env python3

import tkinter as tk
from functools import partial


class Buttons(tk.Frame):
    def __init__(self, master):
        tk.Frame.__init__(self, master)
        self.pushed_buttons = []
        self.buttons = []
        for number in range(5):
            button = tk.Button(self, text=f'Nr. {number}', command=partial(self.show_number, number))
            button.grid(column=number, row=0)
            self.buttons.append(button)

    def show_number(self, number):
        if not number in self.pushed_buttons:
            self.pushed_buttons.append(number)
            print(number)
        else:
            print('Button wurde schon gedrückt')


def main():
    root = tk.Tk()
    root.title('Nur mal so zum zeigen')
    app = Buttons(root)
    app.pack()
    app.mainloop()


if __name__ == "__main__":
    main()

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
emmamenden
User
Beiträge: 16
Registriert: Mittwoch 15. August 2018, 05:39

Code: Alles auswählen

#-------------------------------------------------------------------------------

def calculate_matrix():

    #  Der Reihe nach die 8 Nachbarn der aktuellen Zelle betrachten
    #  und die Zustände in n[1] bis n[8] speichern (1 oder 0)
    #  Ist der Index der zu betrachtenden Zelle  x < 0 dann ist x = spalten-1  
    #                                            y < 0 dann ist y = zeilen-1
    #  ist der Index der zu betrachtenden Zelle  x > (spalten-1) dann ist x = 0
    #                                            y > (zeilen-1)  dann ist y = 0
    #
    #  drei Regeln:
    #       Zelle bleibt leben, wenn Summe                                   n = 2 oder 3
    #       Zelle wird geboren mit genau 3 Nachbarn                          n = 3(Überschneidung mit oberer Regel)
    #       Zelle stirbt mit mehr als 3 Nachbarn oder weniger als 2 Nachbarn n > 3 oder n < 2
    #


    for zeile in range(zeilen):                                 # Achtung Anzahl spalten(36)/zeilen(20): 
        for spalte in range(spalten):                           # heisst: x von 0..35, y von 0..19
            matrix_neu[zeile][spalte] = 0
            

    for zeile in range(zeilen):
        for spalte in range(spalten):                           #     Schema Position
                                                                #
            #Position 1                                         #     1 2 3
            x_pos = spalte-1                                    #     4 x 5       x = aktuelle Zelle
            y_pos = zeile-1                                     #     6 7 8
            if x_pos < 0: 
                x_pos = spalten-1                      
            if y_pos < 0: 
                y_pos = zeilen-1                      
            n[1] = matrix_ist[y_pos][x_pos]                      
            
             #Position 2                         
            x_pos = spalte
            y_pos = zeile-1                      
            if y_pos < 0: 
                y_pos = zeilen-1 
            n[2] = matrix_ist[y_pos][x_pos]
            
             #Position 3                         
            x_pos = spalte+1
            y_pos = zeile-1
            if x_pos > (spalten-1): 
                x_pos = 0
            if y_pos < 0: 
                y_pos=zeilen-1 
            n[3] = matrix_ist[y_pos][x_pos]
            
             #Position 4                         
            x_pos = spalte-1
            y_pos = zeile
            if x_pos < 0: 
                x_pos = spalten-1
            n[4] = matrix_ist[y_pos][x_pos]
            
             #Position 5                        
            x_pos = spalte+1
            y_pos = zeile
            if x_pos > (spalten-1): 
                x_pos = 0
            n[5] = matrix_ist[y_pos][x_pos]
            
             #Position 6                        
            x_pos = spalte-1
            y_pos = zeile+1
            if x_pos < 0: 
                x_pos = spalten-1
            if y_pos > (zeilen-1): 
                y_pos = 0 
            n[6] = matrix_ist[y_pos][x_pos]
            
             #Position 7                        
            x_pos = spalte
            y_pos = zeile+1
            if y_pos > (zeilen-1): 
                y_pos = 0
            n[7] = matrix_ist[y_pos][x_pos]
            
             #Position 8                        
            x_pos = spalte+1
            y_pos = zeile+1
            if x_pos > (spalten-1): 
                x_pos = 0
            if y_pos > (zeilen-1): 
                y_pos = 0
            n[8] = matrix_ist[y_pos][x_pos]
            
            summe = 0
            for k in range(8):      # k = 0..7
                summe = summe + n[k+1]
                
                
            # Die drei Regeln:
            
            #1.) Zelle unverändert, wenn summe 2 oder 3 (Überschneidung mit Regel 2!)

            if summe == 2:
                matrix_neu[zeile][spalte] = matrix_ist[zeile][spalte]
            
            #2.) Zelle wird auf jeden Fall 1, wenn summe genau 3
            
            if summe == 3:
                matrix_neu[zeile][spalte] = 1
                
            #3.) Zelle stirbt, wenn Summe > 3
            
            if summe > 3:
                matrix_neu[zeile][spalte] = 0
                
            #4.) Zelle stirbt, wenn n<2
            
            if summe <2:
                matrix_neu[zeile][spalte] = 0
                
            if summe == 3:
                print("Zeile:",zeile,"     Spalte:",spalte,"     Nachbarn:",summe)
                

    for zeile in range(zeilen):
        for spalte in range(spalten):
          matrix_ist[zeile][spalte] = matrix_neu[zeile][spalte]  
          
   

Hallo nochmal... mein "Ziel" habe ich erreicht, dieses Conway Game of Life läuft....

Ein Problem hatte ich der Betrachtung der Feldgrenzen, also, wenn der Arrayindex des zu betrachtenden Nachbarn <0 oder >"zeilen-1" oder "spalten-1"

In x- Richtung funktionierte das auch, in y-Richtung nicht... alles richtig, ging aber nicht... bis ich dann festgestellt habe, dass ich y_pos und Y-pos verwendet habe und der Untetrschied kaum zu sehen war; schade, dass man in python nicht deklarieren muss...
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@emmamenden: Funktionen (und Methoden) bekommen alles was sie ausser Konstanten benötigen als Argument(e) übergeben. Auf Modulebene darf es keine Variablen geben, da gehört nur Code hin der Konstanten, Funktionen, und Klassen definiert.

`calculate_matrix()` ist deswegen keine richtige Funktion, sondern nur ein Name für ein Stück Code. Was das ganze ziemlich unübersichtlich macht.

`zeilen` und `spalten` sollte man von der Matrix abfragen, damit man sicher sein kann, dass die Werte davon mit den tatsächlichen Dimensionen der Matrix übereinstimmen.

Statt eine Matrix mit 0en zu füllen und am Ende dann die Werte Element für Element wieder in die Ausgangsmatrix zu kopieren, würde man einfach immer eine neue Matrix erstellen und am Ende einfach die alte Matrix durch die neue ersetzen. Die Funktion würde die alte Matrix als Argument bekommen, und eine neu erstellte Matrix als Ergebnis liefern.

Statt da 8 mal fast den gleichen Code für jede Position zu schreiben bzw. zu kopieren, würde man das mit einer Schleife über die Delta-Werte der Positionen lösen wo der Code nur *einmal* steht.

Und auch gleich die Summe ausrechnen und nicht erst alles umständlich in einer globalen Liste speichern die den wirklich schlechten Namen `n` trägt und zudem noch aus irgendwelchen Gründen erst bei Index 1 mit sinnvollen Werten belegt wird. Selbt wenn man die 1en und 0en in einer Liste sammeln würde, dann würde man das mit einer leeren Liste beginnend mit `append()` machen, und am Ende einfach die `sum()`-Funktion zum addieren verwenden.

Die Umsetzung der Regeln mit den vielen ``if``\s ist umständlich. Ob lebendig oder tot kann man als *eine* Bedingung ausdrücken und dann auch gleich das Ergebnis speichern, denn eigentlich sind das ja gar keine 0 und 1 Werte sondern Wahrheitswerte. Dafür sollte man 0 und 1 nicht missbrauchen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
emmamenden
User
Beiträge: 16
Registriert: Mittwoch 15. August 2018, 05:39

Schönen Dank für die Hinweise, werde ich mal ausprobieren!
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

Hallo Peter,
peterpy hat geschrieben: Donnerstag 1. Juli 2021, 09:18

Code: Alles auswählen

    root = tk.Tk()     
bitte versteh' mich nicht falsch, ich möchte Dir wirklich nicht auf die Füße treten. Deine Lösung gefällt mir (halbwegs) und ich weiß, daß grob geschätzt 99,9% aller Tkinter-Beispiele prozedural implementiert sind. Aber ich finde das grausam... wie gesagt: sei mir nicht böse, bitte. Bei GUIs halte ich Objektorientierung für dringendst angeraten. Tkinter war dabei über lange Jahre hinweg lang ein Mistfink, weil die Komponenten nicht von builtins.object geerbt haben und deswegen so etwas häßliches gebraucht haben wie

Code: Alles auswählen

class Dings(tkinter.Tk):
    def __init__(self, *args, **kwargs):
        tkinter.Tk.__init__(self, *args, **kwargs)
	[...]
Das ist aber heute nicht mehr so, und deswegen kann man das auch hübsch mit moderner OO schreiben, also mit super():

Code: Alles auswählen

#!/usr/bin/env python

import tkinter as T

class Action:
    def __init__(self, topwin, x, y):
        self.topwin = topwin
        self.x = x
        self.y = y

    def __call__(self, *args, **kwargs):
        self.topwin.xy[self.x][self.y].config(foreground='white', background='green')
        self.topwin.update()


class MainWin(T.Tk):    
    def __init__(self, x=1, y=1, *args, **kwargs):
        self.x = x
        self.y = y
        super().__init__(*args, **kwargs)
        self.xy = []
        for x in range(self.x):
            frame = T.Frame(self)
            frame.pack(side=T.TOP)
            col = []
            for y in range(self.y):
                btn = T.Button(frame, text='•', command=Action(self, x, y))
                btn.pack(side=T.LEFT)
                col.append(btn)
            self.xy.append(col)


if __name__ == '__main__':
    MainWin(25, 5).mainloop()
Nach meinem Eindruck wissen leider nur wenige Entwickler, daß das mittlerweile so einfach geht, und deswegen sehen wir in Foren wie diesem auch immer wieder den prozeduralen Stil -- auch wenn er dann am Ende gar nicht so gänzlich prozedural ist, aber sei's drum... ;-)
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das GUIs eher früher als später OO brauchen steht außer Frage.

Aber das nun durch eine überkomplizierte re-Implementierung von functools.partial, unnötigem Zustand in den Objekten, und überflüssigen update-Aufrufen belegen zu wollen, ist ein bisschen ulkig.

Aber wenn man nur einen 🔨 hat, wird halt genagelt…
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@LukeNukem: tkinter wird immer und überall mit `tk` abgekürzt nicht mit `T`, von der Schreibweise her wäre das eine Konstante und kein Modul. `x` und `y` sind äußerst schlechte Variablennamen. Außerdem sind die üblichen Vorstellungen, dass `x` was ist was nach rechts geht und `y` was ist was nach unten geht, vertauscht. Wenn man so etwas tabellenartiges hat, möchte man eher `grid` benutzten.
Ob `super` so super ist, wird hier immer wieder diskutiert. Deshalb Mistfink Superhypermodern gegenüberzustellen ist auf jeden Fall übertrieben. Die Klasse `Action` ist überflüssig und weiß zu viel von `MainWin`.

Code: Alles auswählen

import tkinter as tk
from functools import partial

class MainWin(tk.Tk):    
    def __init__(self, row_count=1, column_count=1, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.table = []
        for row_index in range(row_count):
            column = []
            for column_index in range(column_count):
                button = tk.Button(self, text='•', command=partial(self.update_button_color, row_index, column_index))
                button.grid(row=row_index, column=column_index)
                column.append(button)
            self.table.append(column)

    def update_button_color(self, row_index, column_index):
        self.table[row_index][column_index].config(foreground='white', background='green')

def main():
    mainwin = MainWin(25, 5)
    mainwin.mainloop()

if __name__ == '__main__':
    main()
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

Ja, simple und klar.

Aber mal was anderes. Wenn schon OOP, warum nicht CurstomWidgets? Spricht etwas dagegen? Die Funktionalität ist im Widget gekapselt und "MainWindow" ist noch ein bisschen aufgeräumter.

Code: Alles auswählen

import tkinter as tk


class CustomButton(tk.Button):
    def __init__(self, row, column, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self["command"] = self.toggle_face
        self["text"]= "•"
        self.grid(row=row, column=column)

    def toggle_face(self):
        if self["bg"] == "green":
            self["bg"] = "SystemButtonFace"
        else:
            self["bg"] = "green"


class MainWindow(tk.Tk):
    def __init__(self, rows, columns, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for row in range(rows):
            for column in range(columns):
                CustomButton(row, column)


app = MainWindow(10, 10)
app.mainloop()
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich persönlich vermeide Ableitung nach Möglichkeit. Aus den üblichen Gründen. Composition over Inheritance, Boilerplate vermeiden, mehfachvererbung ist tricky, lose coupling ist gut, Open/close Prinzip und so weiter.
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Bezüglich `super()` sollte man vielleicht auch noch sagen, dass das mit `tkinter` nicht richtig funktioniert wenn man versucht das wirklich zu benutzen, denn die `tkinter`-Klassen *selbst* benutzen das nicht. Dafür wofür `super()` eigentlich da ist, sicherzustellen, dass bei Mehrfachvererbung bei allen beteiligten Typen die entsprechenden Methoden aufgerufen werden, funktioniert nur richtig, wenn auch *alle* Typen `super()` in den Methoden verwenden. Bei der `__init__()` ist es insbesondere wichtig, dass *alle* Klassen in der Hierarchie auch die `object.__init__()` via `super()` erreichen können. Und das tun die Klassen in `tkinter` nicht.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

__blackjack__ hat geschrieben: Freitag 10. September 2021, 10:11 Bezüglich `super()` sollte man vielleicht auch noch sagen, dass das mit `tkinter` nicht richtig funktioniert.
Die Entwickler sehen das anders, fürchte ich: https://docs.python.org/3/library/tkinter.html
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Da in der Basisklasse von Button das super() fehlt, haben die Entwickler es wohl verbockt. https://github.com/python/cpython/blob/ ... _.py#L2588

Was ja der Punkt von __blackjack__ war.
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Dokumentation ist ganz sicher falsch, denn Basisklassen werden über BasisKlasse.__init__(...) aufgerufen: https://github.com/python/cpython/blob/ ... _.py#L2649
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

Sirius3 hat geschrieben: Freitag 10. September 2021, 11:44 Die Dokumentation ist ganz sicher falsch, denn Basisklassen werden über BasisKlasse.__init__(...) aufgerufen: https://github.com/python/cpython/blob/ ... _.py#L2649
Unter Python2 hat super() mit Tkinter nicht funktioniert. Wenn ich mich recht entsinne (ich bin zu faul, das jetzt nochmal zu recherchieren, zumal Python2 ja ohnehin EOL ist), dann war das deswegen, weil Frederik Lundhs Tkinter-Klassen nicht von object geerbt haben, was aber die Voraussetzung für die Verwendung von super() war. Heute sind diese expliziten Aufrufe nicht mehr notwendig, denn in Python3 erben alle Klassen ohnehin implizit von object und erfüllen damit die Voraussetzungen, die super() braucht. Insofern sind also sowohl der alte Code als auch die neue Dokumentation absolut korrekt.
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@LukeNukem: Das stimmt so nicht. Von `object` zu erben ist eine notwendige Bedingung, die alleine reicht aber nicht aus. Es müssen auch alle beteiligten Methoden selbst `super()` benutzen. Sonst funktioniert `super()` nur so halb, also eben nicht so super. Der Name ist auch irreführend. Das ruft nicht zwingend die Methode in der Superklasse auf, sondern die nächste Methode in der „method resolution order“ (MRO). In der Programmiersprache Dylan, wo das her kommt, heisst das deswegen auch `next-method` und nicht `super`.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

__blackjack__ hat geschrieben: Samstag 11. September 2021, 15:44 @LukeNukem: Das stimmt so nicht.
Da ich das mit dem OOP-Tkinter schon seit ziemlich langer Zeit genau so mache, wie es auch die Dokumentation von Python3 zeigt, und ich damit noch nie ein Problem gehabt habe, gehe ich weiterhin davon aus, daß die Dokumentation korrekt ist und demzufolge auch meine Ausführungen dazu korrekt sind. Allerdings kannst Du mich gerne überzeugen, indem Du ein lauffähiges Codebeispiel postest, welches zeigt, daß "super().<methode>(*args, **kwargs)" tatsächlich etwas anderes tut als "Basisklasse.<methode>(self, *args, **kwargs)". Viel Erfolg! ;-)
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@LukeNukem:

Code: Alles auswählen

#!/usr/bin/env python3
from tkinter import Tk, Button


class SomeMixin:
    def __init__(self, *args, **kwargs):
        print("SomeMixin init")
        super().__init__(*args, **kwargs)


class SpecializedButton(Button, SomeMixin):
    def __init__(self, *args, **kwargs):
        print("SpecialButton init")
        super().__init__(*args, **kwargs)


def main():
    root = Tk()
    button = SpecializedButton(root)
    button.pack()
    root.mainloop()


if __name__ == "__main__":
    main()
Die `SomeMixin.__init__()` wird nicht aufgerufen. Und zwar weil `Button` kein `super()` benutzt. Damit `super()` so funktioniert wie es soll, muss wie schon mal gesagt, jede betroffene Methode `super()` verwenden. Sonst kann `super()` nicht das machen wofür es gedacht ist.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

__blackjack__ hat geschrieben: Samstag 11. September 2021, 18:07 Die `SomeMixin.__init__()` wird nicht aufgerufen. Und zwar weil `Button` kein `super()` benutzt. Damit `super()` so funktioniert wie es soll, muss wie schon mal gesagt, jede betroffene Methode `super()` verwenden. Sonst kann `super()` nicht das machen wofür es gedacht ist.
Das hat aber nichts mit Tkinter zu tun, sondern mit Mehrfachvererbung und den subtilen Spezialitäten, wie super() die MRO abarbeitet: die wird nämlich von links nach rechts durchsucht, und wenn ein Attribut des gewünschten Namens vorhanden ist, eben dieses benutzt. Dieser Code hier hat dasselbe "Problem", obwohl Tkinter offensichtlich gar nicht involviert ist UND alle __init__()-Methoden super().__init__() aufrufen:

Code: Alles auswählen

#!/usr/bin/env python

class P:
    def __init__(self):
        print('      P.__init__()')
        super().__init__()

class F(P):
    def __init__(self):
        print('    F.__init__()')
        super().__init__()
        
class A(F):
    def __init__(self):
        print('  A.__init__()')
        super().__init__()

class B(F):
    def __init__(self):
        print('  B.__init__()')
        super().__init__()
        
class C(A, B):
    def __init__(self):
        print('C.__init__()')
        super().__init__()

        
if __name__ == '__main__':
    c = C()

Code: Alles auswählen

C.__init__()
  A.__init__()
  B.__init__()
    F.__init__()
      P.__init__()
Du siehst: es ist alles eine Frage der MRO, und super() ist nicht dazu da, explizite Aufrufe von Methoden der Parent-Klasse(n) zu ersetzen, wie Tkinter und etliche andere Libraries mit Mehrfachvererbung sie benutzen. Aber Mehrfachvererbung ist in jeder Sprache, die ich kenne und die sie unterstützt, eine recht komplexe Angelegenheit, selten nötig -- deswegen unterstützen manche Sprachen auch gar keine Mehrfachvererbung -- und eher etwas für Leute, die wissen, was sie tun. Übrigens, da Du schon Mixins erwähnst: ein Mixin ist nach meinem Verständnis im Kern eine Klasse, die zu vorhandenen Klassen ein paar Convenience-Methoden oder -Attribute hinzufügt, und gerade nicht dazu gedacht, vorhandene Methoden oder Attribute anderer Klassen zu überschreiben wie in Deinem Beispiel, aber das nur am Rande bemerkt.

Aber, wie gesagt: alles eine Frage der MRO. Insofern läßt sich das Problem aus Deinem Codebeispiel ganz einfach lösen, indem Du stattdessen:

Code: Alles auswählen

class SpecializedButton(SomeMixin, Button):
schreibst. Dann funktioniert alles wie gewünscht.
Antworten