Button auslesen
-
- User
- Beiträge: 16
- Registriert: Mittwoch 15. August 2018, 05:39
Schönen Dank für die Hinweise, werde ich mal ausprobieren!
Hallo Peter,
Das ist aber heute nicht mehr so, und deswegen kann man das auch hübsch mit moderner OO schreiben, also mit super():
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...
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)
[...]
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()
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…
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…
@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`.
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()
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.
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()
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.
- __blackjack__
- User
- Beiträge: 13190
- 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.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
Die Entwickler sehen das anders, fürchte ich: https://docs.python.org/3/library/tkinter.html__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.
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.
Was ja der Punkt von __blackjack__ war.
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.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
- __blackjack__
- User
- Beiträge: 13190
- 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`.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
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!
- __blackjack__
- User
- Beiträge: 13190
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@LukeNukem:
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.
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()
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
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:__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.
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__()
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):
- __blackjack__
- User
- Beiträge: 13190
- 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.
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.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
Es ist halt wichtig zu verstehen, wie die Serialisierung erfolgt. Nehmen wir das folgende abgewandelte Beispiel:
Mit dem Ergebnis:
Nun deaktivieren wir den Aufruf von super() in Q, so wie es analog in vielen tkinter-Klassen erfolgt:
und das Ergebnis schaut auf einmal ganz anders aus
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.
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.
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.
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 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.
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 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.
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!
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!