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.
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: 14545
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: 17754
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: 14545
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: 13117
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.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
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: 14545
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: 17754
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: 13117
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`.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
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: 13117
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.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
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.
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@LukeNukem: Der Code von Dir hat ja offensichtlich *nicht* das selbe Problem, denn da werden alle `__init__()`-Methoden aufgerufen, und zwar egal in welcher Reihenfolge man die Basisklassen hinschreibt. Genau dafür ist `super()` ja da, und genau das klappt nur *immer* wenn alle `super()` benutzen.

Das umdrehen der Reihenfolge der Basisklassen funktioniert in meinem Beispiel auch nur solange `SomeMixin.__init__()` auch `super()` verwendet. Wenn man das da weg liesse, würde es auch wieder nicht gehen. Es müssen halt alle beteiligten Methoden `super()` verwenden, damit die Methoden in der MRO alle aufgerufen werden können.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Es ist halt wichtig zu verstehen, wie die Serialisierung erfolgt. Nehmen wir das folgende abgewandelte Beispiel:

Code: Alles auswählen

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

class F(P):
    def __init__(self):
        print('    F.__init__()')
        super().__init__()
        
class Q:
    def __init__(self):
        print('    Q.__init__()')
        super().__init__()
        
class A(Q):
    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()

Mit dem Ergebnis:

Code: Alles auswählen

C.__init__()
  A.__init__()
    Q.__init__()
  B.__init__()
    F.__init__()
      P.__init__()


Nun deaktivieren wir den Aufruf von super() in Q, so wie es analog in vielen tkinter-Klassen erfolgt:

Code: Alles auswählen

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

class F(P):
    def __init__(self):
        print('    F.__init__()')
        super().__init__()
        
class Q:
    def __init__(self):
        print('    Q.__init__()')
        #super().__init__()
        
class A(Q):
    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()

und das Ergebnis schaut auf einmal ganz anders aus

Code: Alles auswählen

C.__init__()
  A.__init__()
    Q.__init__()

da der Aufruf von object.super() in Q unterbrochen wurde.

Ich bin durchaus ein Verfechter von super(), aber man muß eben wissen, wie die MRO-Serialisierung funktioniert. Bei rein linearen Vererbungen klappt super() immer, auch wenn es falsch eingesetzt wird.
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

kbr hat geschrieben: Samstag 11. September 2021, 20:55 Nun deaktivieren wir den Aufruf von super() in Q, so wie es analog in vielen tkinter-Klassen erfolgt:
Naja, das ist aus meiner Sicht leider nicht ganz richtig. Tkinter ruft die Methoden (bzw. Konstruktoren) der Elternklassen (je nach Bedarf) nicht über super(), sondern explizit auf, was im Zweifelsfall ohnehin immer die sicherste und zuverlässigste Möglichkeit ist. Sie werden also aufgerufen, und das funktioniert wunderbar. Daran ändert sich auch nichts, wenn von Tkinters Klassen erbende Kindklassen für den Aufruf von Elternmethoden super() benutzen -- darauf muß man nur achten, wenn man selbst Mehrfachvererbung benutzt, woran __blackjack__ und freundlicherweise noch einmal erinnert hat. Am Ende hat das aber, wie gesagt, nichts mit Tkinter zu tun, sondern mit Mehrfachvererbung, der MRO und der Spezialitäten von super() -- das sich trotz allem immer noch wundernbar mit Tkinter nutzen läßt.

Am Ende spielt es für den Punkt, den ich ursprünglich adressiert habe, aber ohnehin keine Rolle: dabei ging es nämlich darum, daß viel zu viele Tutorials und damit auch viel zu viele Anwender Tkinter in dieser häßlichen, unübersichtlichen und kontraproduktiven prozeduralen Weise einsetzen. Dabei kann die Objektorientierung ihre Stärken gerade auch bei der Entwicklung von GUI-Software besonders gut ausspielen und für eine wesentlich bessere Codeorganisation, viel mehr Übersichtlichkeit und eine höhere Wiederverwendbarkeit sorgen.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

LukeNukem hat geschrieben: Montag 13. September 2021, 17:51 Naja, das ist aus meiner Sicht leider nicht ganz richtig. Tkinter ruft die Methoden (bzw. Konstruktoren) der Elternklassen (je nach Bedarf) nicht über super(), sondern explizit auf, was im Zweifelsfall ohnehin immer die sicherste und zuverlässigste Möglichkeit ist. Sie werden also aufgerufen, und das funktioniert wunderbar.
Es funktioniert nicht wunderbar, da die Methoden der tkinter-Elternklassen ihrerseits nicht object.__init__ aufrufen; genau das hatte ich mit dem Beispiel gezeigt. Dieser Aufruf ist aber erforderlich, damit super im Falle von Mehrfachvererbung in Verbindung mit tkinter-Klassen korrekt funktionieren kann. Allein dadurch, dass der Python 2 old-style-classes tkinter-code mit Python 3 auf einmal implizit von object erbt, ändert sich gar nichts. tkinter ist schlicht und einfach nicht für Mehrfachvererbung in Verbindung mit der MRO-Serialisierung von super entworfen. Und der explizite Aufruf von Methoden der Elternklassen ist nicht die sicherste und zuverlässigste Möglichkeit, sondern lediglich die Explizite.
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

kbr hat geschrieben: Montag 13. September 2021, 20:45 Es funktioniert nicht wunderbar, da die Methoden der tkinter-Elternklassen ihrerseits nicht object.__init__ aufrufen; genau das hatte ich mit dem Beispiel gezeigt. Dieser Aufruf ist aber erforderlich, damit super im Falle von Mehrfachvererbung in Verbindung mit tkinter-Klassen korrekt funktionieren kann. Allein dadurch, dass der Python 2 old-style-classes tkinter-code mit Python 3 auf einmal implizit von object erbt, ändert sich gar nichts.
Ich nehme Deinen Einwand zur Kenntnis, aber bitte sei mir nicht böse, wenn ich jetzt einmal einen ehemaligen Bundesminister des Auswärtigen zitieren muß: ich bin nicht überzeugt. Ich nutze Tkinter seit langer Zeit nur noch objektorientiert und habe, seit ich Python3 nutze, niemals Probleme mit super() gehabt. Mir geht allerdings die Hybris ab, das als Beleg werten oder gar anführen zu wollen, das wäre natürlich ein argumentativer Fehlschluß und dieses hohen Hauses unwürdig. Daß die offizielle Dokumentation zu Tkinter diese Technik ebenfalls empfiehlt, wäre insofern allenfalls ein argumentum ad populum.
kbr hat geschrieben: Montag 13. September 2021, 20:45 tkinter ist schlicht und einfach nicht für Mehrfachvererbung in Verbindung mit der MRO-Serialisierung von super entworfen.
Das sagst Du, und andere Menschen in diesem Forum ebenfalls. Mir dagegen fällt es aufgrund meiner gegenteiligen Erfahrungen und der Tatsache, daß es in der Python-Dokumentation genau so gezeigt wird, schwer, das zu glauben (ganz abgesehen davon, daß das nicht mein Punkt war, aber sei's drum). Da wir hier aber in einem Forum zu einer ausgesprochen zuverlässigen Programmiersprache sind, die sich als enorm stabil erwiesen hat und in der Regel absolut reproduzierbare Ergebnisse hervorbringt, möchte ich auch Dich sehr herzlich darum bitten, mir Deine Aussagen als Beispiel in lauffähigem Code zu demonstrieren. Und zwar bitte nicht wie Dein Vorgänger unter Mißachtung der MRO, sondern bitte an Code, der Deine Ausführungen ganz konkret an und mit Tkinter zeigt. Wenn Du das kannst, dann mache ich mir die Arbeit -- auf Wunsch natürlich mit ausdrücklicher Bezugnahme auf Dich -- einen Bugreport zu verfassen, damit die Dokumentation korrigiert wird. Lieben Dank für Deine Bemühungen!
kbr hat geschrieben: Montag 13. September 2021, 20:45 Und der explizite Aufruf von Methoden der Elternklassen ist nicht die sicherste und zuverlässigste Möglichkeit, sondern lediglich die Explizite.
Ja, genau -- und dieses Explizite macht es aus meiner Perspektive zur sichersten und zuverlässigsten Möglichkeit. super() ist etwas, das primär den "Faulpelzen" mit ihrem Streben nach wartbarem und wiederverwendbarem Code hilft. Befremdliche Pedanten wie ich, die PEP20 mögen ("explicit is better than implicit"), mögen die explizite Variante prinzipiell lieber, denn wenn man es richtig macht, funktioniert sie immer und gibt uns als Entwicklern die präziseste mögliche Kontrolle darüber, was, wann, wo und wie aufgerufen wird! ;-)
Antworten