Klassen. Attribute, Methoden für eine tkinter GUI? Wozu?

Fragen zu Tkinter.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Ohne Klassen. Attribute, Methoden und Namen hat man ein Höchstmaß an Kapselung!

Die Gliederung in Module ist angebracht bei komplexen großen GUIs. Da kann man Klassen verwenden oder auch nicht. Denn ein Widget anlegen und dann eine init Funktion in einem anderen Modul aufrufen geht genauso gut. Und bietet einen großen Vorteil.

Es existiert eine reine tkinter GUI ohne zusätzlich Verweise über Attributnamen und Widgetnamen - die sollte man dann auch nicht nehmen. Über einen benannten Objektbaum zugreifen kann man dann nicht. Über die Children Liste von tkinter zugreifen, bringt auch nichts, da mit nichtssagenden Objektreferenzen nichts anzufangen ist. Wenn keine zusätzlichen Attribute verwendet werden und auch keine Methoden sind auch darüber keine Zugriffe von außen möglich. Denn auf das Innere von Closures gibt es nur Zugriffe mittels im Closure vereinbarten Callbacks, also Command Callbacks, Event Callbacks, Message Callbacks.

Damit ist dann ein Höchstmaß an Kapselung erreicht und damit ist kreuz und quer Programmierung nicht mehr möglich - sondern Aufrufe gibt es dann nur mehr über wohl definierte Message Interfaces. Eine komplexe GUI sollte dann auch ohne Probleme leicht zu programmieren sein.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Natürlich spricht nichts dagegen im inneren der Closures auch Klassen einzusetzen oder statt der Closure ein referenzloses Objekt, denn da kommt man auch von außen nicht ran.
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

Man sollte hier im Forum eine Ab-18-Kategorie einführen, nicht dass das jemand liest, der noch nicht 18 Jahr Pythonerfahrung hat und bleibende Schäden davonträgt.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:Man sollte hier im Forum eine Ab-18-Kategorie einführen, nicht dass das jemand liest, der noch nicht 18 Jahr Pythonerfahrung hat und bleibende Schäden davonträgt.
Ich bin aber schon sehr frühzeitig darauf gekommen, wie man in Python gut kapseln kann. Wenn man Methoden von Objekten in Callbacks referenziert und das Objekt selber nirgends, dann bleibt das Objekt im Speicher aber Zugriff von außen gibt es keinen. Die Referenz auf den Callback mag man zwar herausbekommen aber den kann man auch auf die spezifizierte Art und Weise aufrufen das Objekt selber bekommt man aber nicht. Und wenn man dann den Callback löscht, etwa im Eventbroker für die Message einen anderen einträgt, ist das Objekt weg im Garbage Collektor. Und wenn man die Source für die Erzeugung des Objektes nicht in einem Modul hat, sondern das Objekt durch Scriptausführung erzeugt hat - aber nicht Mainscript, ist auch vom Code im Speicher nichts mehr da. Bei einem Widget wäre natürlich das Widget noch da, solange man kein destroy ausführt.

Ich finde Objekte gut, auf die man nicht zugreifen kann und die man spurlos beseitigen kann und den Code im Speicher dazu auch.

Man könnte also eine GUI schreiben mit zigtausenden von Screens mit soviel Python Code, wie die Festplatte hergibt. Aber wenn man immer nur einen Screen im Speicher hat, ist alles kein Problem.

Ist doch ganz einfach: eine lokal aufgerufene Funktion oder Klasse wird durch Bindung einer ihrer Mehoden oder Funktionen an einen Callback zu einem weiter bestehenden Objekt. Auch eine Closure ist dann so ein Objekt.
Zuletzt geändert von Alfons Mittelmeyer am Dienstag 22. August 2017, 12:22, insgesamt 1-mal geändert.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Sirius3 hat geschrieben:Man sollte hier im Forum eine Ab-18-Kategorie einführen, nicht dass das jemand liest, der noch nicht 18 Jahr Pythonerfahrung hat und bleibende Schäden davonträgt.
Ich betrachte Programmieren auch als Kunst. Da gibt es realistische, abstrakte bis abartige Werke. Ich schlage vor in unserem Forum ein neues Subforum zu eröffnet, welches als Gallerie für abweichend Werke dient. Bin mir sicher, dass sich jenes Subforum innert kürzester Zeit mit künstlerisch Beiträgen aufblähen wird.

Gruss wuf :wink:
Take it easy Mates!
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

wuf hat geschrieben:Ich betrachte Programmieren auch als Kunst. Da gibt es realistische, abstrakte bis abartige Werke.
Gruss wuf :wink:
Findest Du meine Ideen abartig? Ich habe doch einen Weg gefunden wie man gut kapseln kann. Dafür kann man auch Klassen einsetzen. Die Methoden und Attribute von Klassen sind öffentlich. Aber wenn das Objekt nicht öffentlich ist, sind sie es auch nicht und so kann man gut kapseln:

Code: Alles auswählen

# statt
self.my_frame = MyFrame(self,**kwargs)
self.my_frame.pack()

# schreibe man einfach
MyFrame(self,**kwargs).pack()

# und schon hat man gut gekapselt
Das kann man nicht nur bei Widget Klassen machen, sondern auch bei x-beliebigen Klassen. Bei Funktionen geht das auch, aber da muß man eine eventuelle Vererbung von einer Basisfunktion selber implementieren. Bei Vererbung sollte man daher besser Klassen nehmen statt Funktionen.
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Findest Du meine Ideen abartig? Ich habe doch einen Weg gefunden wie man gut kapseln kann.
1. Genie und Wahnsinn liegen dicht beiander.
2. Wahre Genies werden zu Lebzeiten oft verkannt -> es gibt also noch die Hoffnung der späten Ehre für dich :-)

Gruß, noisefloor
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

noisefloor hat geschrieben:2. Wahre Genies werden zu Lebzeiten oft verkannt -> es gibt also noch die Hoffnung der späten Ehre für dich :-)
Wäre schön, übrigens ist kaum ein Unterschied zwischen Klassen und Funktionen, denn auch Funktionen können Attribute haben:

Code: Alles auswählen

def function(*args,**kwargs):

    class Attributes:
        pass

    self = Attributes()

    self.a = 5

    def output():
        print(self.a)

    def set_a(value):
        self.a = value

    # statt __init__(self), braucht man bei Funktionen nicht
    output()
    set_a(8)
    output()
    
function()   
Und das Schöne daran ist, dass diese Attribute nicht öffentlich sind. Man kann also auch in Python gut kapseln.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Funktionen kann man auch instanzieren. Im nachfolgenden Beispiel legen wir zwei Instanzen einer Funktion an und testen deren Interface

Code: Alles auswählen

class EventBroker():
    def __init__(self):
        self._dictionary_ = {}
 
    def subscribe(self,message_id,callback):
        self._dictionary_[message_id] = callback
 
    def publish(self,message_id,*args,**kwargs):
        self._dictionary_[message_id](*args,**kwargs)
 
eventbroker = EventBroker()
publish = eventbroker.publish
subscribe = eventbroker.subscribe

def function(function_id,message_id):

    def output(message):
        print(function_id,message)

    # callback interface
    subscribe(message_id,output)        
   

# hier legen wir zwei Instanzen der Funktion an
function('Funktion 1','func1')
function('Funktion 2','func2')

# hier testen wir deren Interface
publish('func1','Guten Morgen')
publish('func2','Gute Nacht')
Zuletzt geändert von Alfons Mittelmeyer am Dienstag 22. August 2017, 15:16, insgesamt 1-mal geändert.
BlackJack

@Alfons Mittelmeyer: Du verwechselst hier Kapselung mit Zugriffsschutz. Kapseln kann man genau so gut mit Klassen, *und* das ist der in Python vorgesehene Weg. Wenn man mehr Zugriffsschutz will, dann will man eine andere Programmiersprache verwenden.

Das `function()`-Beispiel macht keinen Sinn. Da ist nichts wofür man eine Klasse bräuchte.

Zudem haben diese lokalen Funktionen den Nachteil das man sie nicht isoliert testen kann, weder zur Fehlersuche, noch in Unit-Tests.
Benutzeravatar
Kebap
User
Beiträge: 686
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

BlackJack hat geschrieben:Zudem haben diese lokalen Funktionen den Nachteil das man sie nicht isoliert testen kann, weder zur Fehlersuche, noch in Unit-Tests.
Ich bin sicher, Alfons findet einen Weg, auch diese Herausforderung zu meistern!
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Du verwechselst hier Kapselung mit Zugriffsschutz. Kapseln kann man genau so gut mit Klassen, *und* das ist der in Python vorgesehene Weg.
Ich habe nicht behauptet, dass eine Funktion besser wäre als eine Klasse. Ich wollte nur zeigen, dass es auch mit einer Funktion geht. Mit einer Klasse, die man aufruft, ohne die Objektreferenz zu verwenden hat man denselben Zugriffsschutz.
BlackJack hat geschrieben:Das `function()`-Beispiel macht keinen Sinn. Da ist nichts wofür man eine Klasse bräuchte.
Wenn es um Callbacks geht, also um das Binding von Events, Commands und Messages, braucht man dafür keine Klasse. Die Widget Klasse hat man eh schon. In GUIs werden Klassen oft nur verwendet, um den GUI Code besser zu gliedern. Ob man ein Widget anlegt und dann dafür eine init Funktion aufruft oder ob man ein Klasse aufruft, die eine __init__ Methode hat, kommt auf dasselbe heraus. Erst wenn man Zugriff von außen will, also Methodenaufruf oder Vererbung ins Spiel kommt, dann braucht man erst wirklich eine Klasse.
BlackJack hat geschrieben:Zudem haben diese lokalen Funktionen den Nachteil das man sie nicht isoliert testen kann, weder zur Fehlersuche, noch in Unit-Tests
Wieso, ich habe sie doch getestet in Zeile 29 und 30. Und testen geht doch immer so: Signal senden und sehen, ob es tut. Dabei evtl. auch Logfile verwenden. Alles was auf Signale reagiert, muss durch Signale getestet werden. Durch direkten Methodenaufruf testen geht da natürlich nicht. Viele große Anwendungen sind eh nur signalsteuert und mit Methodenaufruf ohne Signale geht da nichts.

Und das mit nicht isoliert testen können stimmt rein gar nicht. Durch diesen Zugriffsschutz erreicht man gerade eine Entkoppelung und Isolierung von anderen Komponenten. Dadurch kann man dann solche Komponenten sehr gut isoliert testen. Messages senden und mit Messages antworten. die anderen Komponenten müssen dazu nicht da sein, weil dort nichts direkt aufgerufen wird, sondern alles über Signale geht.

Ein kleiner Zusatz zum Eventbroker:

Code: Alles auswählen

    def publish(self,message_id,*args,**kwargs):
        if message_id not in self._dictionary_:
            output('EventBroker: no callback defined for message: {},*{},**{}'.format(message_id,args,kwargs))
            return
        self._dictionary_[message_id](*args,**kwargs)
Und schon muss nichts mehr da sein. Print oder Logfile stattdessen und man sieht auch, dass etwas schon tut.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Kebap hat geschrieben:
BlackJack hat geschrieben:Zudem haben diese lokalen Funktionen den Nachteil das man sie nicht isoliert testen kann, weder zur Fehlersuche, noch in Unit-Tests.
Ich bin sicher, Alfons findet einen Weg, auch diese Herausforderung zu meistern!
Den Weg muss ich nicht finden. Signalbasierte Systeme lassen sich sehr einfach testen. Zu Komponenten gibt es Simulatoren, die Messages senden und dann auch Antworten simulieren, ohne dass dafür dann die reale Komponente, etwa ein Steuergerät vorhanden sein muß.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Mit Messages kann man hervorragen isoliert entwickeln und testen. Dazu habe ich mal diesen Remote Manger herauskopiert:

Code: Alles auswählen

# -*- coding: utf-8 -*-

from functools import partial

import sys
def output(param):
    sys.stdout.write(param+'\n')

class EventBroker():
    def __init__(self):
        self._dictionary_ = {}
 
    def subscribe(self,message_id,callback):
        self._dictionary_[message_id] = callback
 
    def publish(self,message_id,*args,**kwargs):
        if message_id not in self._dictionary_:
            output('EventBroker: no callback defined for message: {},*{},**{}'.format(message_id,args,kwargs))
            return
        self._dictionary_[message_id](*args,**kwargs)

eventbroker = EventBroker()
publish = eventbroker.publish
subscribe = eventbroker.subscribe


try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        # widget definitions ===================================
        self.remote_manager = RemoteManager(self)
        self.remote_manager.pack()

class RemoteManager(tk.Frame):

    def __init__(self,master,**kwargs):
        tk.Frame.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.label = tk.Label(self,text='Willkommen im Remote Manager')
        self.button_page_one = tk.Button(self,width=18, text='Benutzereinstellungen')
        self.label_2 = tk.Label(self)
        self.button = tk.Button(self,width=18, text='Bildschirmeinstellungen')
        self.label_3 = tk.Label(self)
        self.button_page_two = tk.Button(self,width=18, text='Servereinstellungen')
        self.label_4 = tk.Label(self)
        self.label.pack(pady=15, padx=25)
        self.button_page_one.pack()
        self.label_2.pack(pady=1, padx=25)
        self.button.pack()
        self.label_3.pack(pady=1, padx=25)
        self.button_page_two.pack()
        self.label_4.pack(pady=5, padx=25)

        # CODE ==============================
        self.button_page_one.config(command=partial(publish,'SHOW_FRAME','page_one'))
        self.button_page_two.config(command=partial(publish,'SHOW_FRAME','page_two'))
        # END CODE ==========================    


if __name__ == '__main__':
    Application().mainloop()
Kann man gleich testen. Siieht man dass da nur zwei Pages implementiert sind und die erste page_one und die dritte page_two heißt und dass die zweite fehlt. Kann man gleich richtig stellen. Denn diese muß ja gar noch nicht da sein.

Kann man auch gleich das Menü fertig machen und auch 'Beenden' eintragen. Kann man Komponenten schon fertig implementieren, ohne dass Gegenstellen da sein müssen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:Zudem haben diese lokalen Funktionen den Nachteil das man sie nicht isoliert testen kann, weder zur Fehlersuche,
noch in Unit-Tests.
Was denkst Du warum ich diesen Aufstand mit dem Zugriffsschutz mache?
Damit man keine kreuz und quer Zugriffe macht.
DAMIT MAN ISOLIERT ENTWICKELT UND ISOLIERT TESTEN KANN!
Zuletzt geändert von Alfons Mittelmeyer am Dienstag 22. August 2017, 20:32, insgesamt 1-mal geändert.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: :lol:
Lach Du nur, schreiben dass man das nicht testen kann, aber anderen empfehlen, dass sie partial benutzen sollen. Also genau das, was man Deiner Meinung nach nicht testen kann.

Ich benutze schon lange mein eigenes partial und brav, wie ich da bin, erzeuge ich ein Objekt und geb eine Methode zurück, Aber partial gibt anscheinend eine Closure Funktionsreferenz zurück. Und das empfiehlst Du?

Hier ein sicheres partial ohne diese angeblich nicht testbaren lokalen Referenzen, denn das ist wirklich ein Objekt:

Code: Alles auswählen

class Callback:

    def __init__(self,function,*args,**kwargs):
        self.function = function
        self.args = args
        self.kwargs = kwargs

    def execute(self,*args,**kwargs):
        dict_kwargs = dict(self.kwargs)
        dict_kwargs.update(kwargs)
        self.function(*list(self.args)+list(args),**dict_kwargs)

def partial(function,*args,**kwargs):
    return Callback(function,*args,**kwargs).execute
Auch hier haben wir wieder den Zugriffsschutz. Man bekommt keine Referenz auf das Objekt, kann aber dessen Methode mittels (*args,**kwargs) aufrufen.
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: man testet ja auch nicht die mit partial veränderte Funktion, denn partial ist schon getestet und es ist sinnvoller die ursprüngliche Funktion zu testen. Was ist der Sinn, partial nachzuprogrammieren? Zugriffsschutz hat nichts mit Kapselung zu tun. Und zum hunderttausendstenmal, das was Du hier veranstaltest ist einfach nur umständlich und da Du keine Ahnung hast, wie Python intern arbeitet, sind Deine Mutmaßungen, was wie funktioniert meist amüsant., haben aber in einem normalen Programm nichts verloren. Auc bei Künstlern gibt, bevor sie die anerkannte Malerei verlassen haben, konnte auch ein Picasso realistisch malen. Bevor Du also anfängst, Deine eigene Programmierphilosophie aufzuschreiben, muß Du erst richtig programmieren lernen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: man testet ja auch nicht die mit partial veränderte Funktion, denn partial ist schon getestet und es ist sinnvoller die ursprüngliche Funktion zu testen. Was ist der Sinn, partial nachzuprogrammieren?
Ich habe nicht partial nachprogrammiert, sondern ich benutze diese Callback Klasse auch in Verbindung mit anderen Funktionen und Klassen. Partial war da nur ein Beiprodukt. Läßt sich auch so schreiben:

Code: Alles auswählen

def partial(function,*args,**kwargs):
    def execute(*new_args,**new_kwargs):
        dict_kwargs = dict(kwargs)
        dict_kwargs.update(new_kwargs)
        function(*list(args)+list(new_args),**dict_kwargs)
    return execute
Außerdem hatte ich des öfteren Probleme mit partial. Funktionierte manchmal nicht. So gut konnte es da ja nicht getestet sein. Aber mein partial ging. Leider ist mir dieser Fehler bei partial in letzter Zeit nicht mehr aufgefallen, sodass ich ihn auch nicht zeigen kann. Evtl. wurde dieser ja bei einem update behoben.
Sirius3 hat geschrieben:Zugriffsschutz hat nichts mit Kapselung zu tun.
Wie kommst Du auf solchen Unsinn?

Laut Wikipedia und nicht nur laut Wikipedia geht es dabei um:
Als Datenkapselung (englisch encapsulation, nach David Parnas auch bekannt als information hiding) bezeichnet man in der Programmierung das Verbergen von Daten oder Informationen vor dem Zugriff von außen. Der direkte Zugriff auf die interne Datenstruktur wird unterbunden und erfolgt stattdessen über definierte Schnittstellen (Black-Box-Modell).
Quelle: https://de.wikipedia.org/wiki/Datenkaps ... mierung%29

Klaro?
Sirius3 hat geschrieben: Und zum hunderttausendstenmal, das was Du hier veranstaltest ist einfach nur umständlich und da Du keine Ahnung hast, wie Python intern arbeitet, sind Deine Mutmaßungen, was wie funktioniert meist amüsant., haben aber in einem normalen Programm nichts verloren. Auc bei Künstlern gibt, bevor sie die anerkannte Malerei verlassen haben, konnte auch ein Picasso realistisch malen. Bevor Du also anfängst, Deine eigene Programmierphilosophie aufzuschreiben, muß Du erst richtig programmieren lernen.
Ja toll, dass meine Programme, die angeblich auf Mutmaßungen aufgebaut sind, so gut funktionieren. Muß wohl an den sogenannten Mutmaßungen etwas dran sein. Naja, Bugs sind natürlich nicht ausgeschlossen haben aber mit der Philosophie des Programmaufbaus nichts zu tun.
Antworten