'command' zwei Argumente übergeben?
Die Kommentare zu Deinen globalen Variablen lassen schlimmes erahnen. Ich kann mir ungefähr vorstellen, wie komplex es ist, die Stacks jeweils richtig zu füllen und wieder abzuräumen. Da die Variablen aber global sind, ist es so gut wie unmöglich, diesen komplexen Code annähernd vollständig zu testen. Ohne Tests ist es nur Glück, wenn das Programm anscheinend funktioniert. Du siehst also, Deine Designentscheidung zieht viele ungünstige Konsequenzen nach sich.
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Am einfachsten wäre es wohl, die zwei Stacks ObjectStack und SelfStack weiterhin zu verwenden. Die sind aber nicht optimal benannt.
Über ObjectStack erhalten Rückrufrobjekte den Zugriff auf sich selbst.
Und über SelfStack haben andere Objekte Zugriff auf sich selbst.
Und wenn man ein anderes Objekt als Rückrufobjekt verwendet, dann hat es Zugriff auf die Rückruffunktionsparameter und auf seine eigenen.
Von unten nach oben wird es schwierig, welches Objekt noch auf dem Rückrufobjekt sitzen könnte. Von oben nach unten allerding wäre es einfach.
Das Objekt könnte einfach die beiden Parametertuples über die Stacks entnehmen und dann die Funktion entsprechend aufrufen durch Zusamensetzen eines neuen Tuples aus den Tuple Teilen.
Das ist aber auch wieder dumm, ein Tuple läßt sich nicht zusammensetzen, wie eine Liste.
Ließe sich eine Liste auch verwenden? Oder soll man das Tuple zur Laufzeit beim Callback compilieren? Gefällt mir nicht. Das mit der Liste sollte man einmal ausprobieren
Über ObjectStack erhalten Rückrufrobjekte den Zugriff auf sich selbst.
Und über SelfStack haben andere Objekte Zugriff auf sich selbst.
Und wenn man ein anderes Objekt als Rückrufobjekt verwendet, dann hat es Zugriff auf die Rückruffunktionsparameter und auf seine eigenen.
Von unten nach oben wird es schwierig, welches Objekt noch auf dem Rückrufobjekt sitzen könnte. Von oben nach unten allerding wäre es einfach.
Das Objekt könnte einfach die beiden Parametertuples über die Stacks entnehmen und dann die Funktion entsprechend aufrufen durch Zusamensetzen eines neuen Tuples aus den Tuple Teilen.
Das ist aber auch wieder dumm, ein Tuple läßt sich nicht zusammensetzen, wie eine Liste.
Ließe sich eine Liste auch verwenden? Oder soll man das Tuple zur Laufzeit beim Callback compilieren? Gefällt mir nicht. Das mit der Liste sollte man einmal ausprobieren
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Also sorry, ob jemand den Stack benutzen will oder Variablen ist Sache des Programmieres. Das kann jeder, der etwas programmieren will, selber entscheiden.Sirius3 hat geschrieben:Die Kommentare zu Deinen globalen Variablen lassen schlimmes erahnen. Ich kann mir ungefähr vorstellen, wie komplex es ist, die Stacks jeweils richtig zu füllen und wieder abzuräumen. Da die Variablen aber global sind, ist es so gut wie unmöglich, diesen komplexen Code annähernd vollständig zu testen. Ohne Tests ist es nur Glück, wenn das Programm anscheinend funktioniert. Du siehst also, Deine Designentscheidung zieht viele ungünstige Konsequenzen nach sich.
Die beiden anderen Stacks ObjectStack und SelfStack jedoch sind völlig unbedenklich und die sind auch nicht zum Benützen gedacht. Sollte wohl _ObjectStack und _SelfStack schreiben, die sind nämlich nicht zum normalen Anfassen da. Auf den ObjectStack wird eine Rückruffunktion gehoben, wenn sie ausgeführt wird und dann kommt sie wieder herunter. Das ist alles, was da geschieht.
Das ist das self des Rückrufobjektes. Oder benützt Du vielleicht self bei Deinen Programmen nicht, weil es wahrscheinlich über einen Stack realisiert ist? Benützt Du vielleicht keine Funktionen oder lokale Variablen, weil da auch der Stack im Spiel ist?
Über die Verwendung von Stack kann man streiten. Aber dass Objekte ein self haben sollen, sollte eigentlich selbstverständlich sein.
Wenn man natürlich für alles eine richtige Klasse machen würde, dann könnte man das original self benützen. Also für jeden CallBack eine Klasse definieren, damit man ein original self hat, kann man selbstverständlich auch. Und gerade das wird von DynTkInter sogar noch unterstützt. Anstatt Funktionen oder Strings, die dann kompiliert werden, dürfen es vor allem Klassen mit einer Methode execute sein. Denn Strings oder Funktionen werden in Instanzen einer solchen Klasse verwandelt. Also ruhig eigene Klassen mit original self schreiben, denn diese Stacks bieten nur den Ersatz für das self.
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Also stimmt nicht ganz. Ein Callback Objekt erhält überhaupt nichts, nur wenn man es so implementiert:
Aber Dein Einwurf war gut, denn jetzt habe ich mich erinnert, dass es gar keine Daten auf diesem Stack gibt und es nur der Zugriiff auf die eigenen Daten ist.
Dann brauche ich den Stack vielleicht doch nicht.
Allerdings gibt es da ein Huckepack Objekt in self.evcode. Also das eine Objekt ist das Callback Objekt und das zweite ist das Callback Ausführungsobjekt, das auch wieder Daten haben kann.
Und mit diesem Stack erhält dann das Ausführungsobjekt den Zugriff auf das Callback Objekt.
Code: Alles auswählen
def execute(self):
ObjectStack.append(self)
self.evcode.execute()
ObjectStack.pop()
Dann brauche ich den Stack vielleicht doch nicht.
Allerdings gibt es da ein Huckepack Objekt in self.evcode. Also das eine Objekt ist das Callback Objekt und das zweite ist das Callback Ausführungsobjekt, das auch wieder Daten haben kann.
Und mit diesem Stack erhält dann das Ausführungsobjekt den Zugriff auf das Callback Objekt.
Zuletzt geändert von Alfons Mittelmeyer am Mittwoch 5. August 2015, 20:57, insgesamt 1-mal geändert.
Jeder Programmierer sollte sich das Leben nicht unnötig schwer machen. Natürlich benutzt jeder der Python programmiert Stacks. Aber man muß sich nicht selbst darum kümmern. Wenn man Funktionen benutzt, kann man über Argumente übergeben und muß nicht indirekt mit globalen Variablen arbeiten. Du programmierst Python auf dem Niveau von Assembler.
- jens
- Python-Forum Veteran
- Beiträge: 8502
- Registriert: Dienstag 10. August 2004, 09:40
- Wohnort: duisburg
- Kontaktdaten:
@Alfons Mittelmeyer: Was hältst du davon einfach mal die sourcen von DynTkInter auf github zu packen, statt immer nur davon zu reden?!?
btw. einen GUI Designer für TkInter hätte ich schon gern. Weil bei komplexeren grid Geschichten, mir die Sache zu unübersichtlich wird.
btw. einen GUI Designer für TkInter hätte ich schon gern. Weil bei komplexeren grid Geschichten, mir die Sache zu unübersichtlich wird.
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Ja, Jens ist eine gute Idee. Aber zuvor würde ich gerne beim Gui Creator auch einen schönen Code haben.jens hat geschrieben:@Alfons Mittelmeyer: Was hältst du davon einfach mal die sourcen von DynTkInter auf github zu packen, statt immer nur davon zu reden?!?
btw. einen GUI Designer für TkInter hätte ich schon gern. Weil bei komplexeren grid Geschichten, mir die Sache zu unübersichtlich wird.
Kritisiert wird da in diesem Forum ja viel, aber helfen tut keiner. Daß man immer wieder denselben Funktionsnamen benützen kann, mußte ich selber herausfinden. So kann ich dann viel in schöne Python Funktionen umschreiben.
Und wie man auch so etwas hinbekommt, konnte mir auch keiner sagen:
Code: Alles auswählen
function(*args1,me,event.*args2)
Statt einem tuple kann man bei *args auch eine Liste benützen und die kann man beliebig zusammensetzen. Also append die einen Parameter + append me + append event + append die anderen Parameter.
Das Beispiel von früher funktioniert genauso, wenn man bei SpinboxY eine Liste statt einem tuple nimmt, und über das Sirius so gemeckert hatte, weil ich keine zwei Module gemacht hatte und die eine Variable nicht gleich zu Beginn initialisiert hatte. Aber sorry, nach Tk() tue ich das nicht, denn das ist GUI. Und eine eigene Applikationsklasse für dieses Beispiel schreibe ich auch nicht. Der Knackpunkt ist, dass man auch eine Liste nehmen kann, wie hier bei den Callbacks für SpinboxY:
Code: Alles auswählen
from tkinter import *
# selected widget similar as in DynTkInter
SelectedWidget = None
def this():
global SelectedWidget
return SelectedWidget
# universal GuiCallback Class for GuiCallbacks and GuiEventCallbacks for functions with variable number of parameters
class GuiCallback:
def __init__(self,widget,function,parameters=()):
self.widget = widget
self.function=function
self.parameters = parameters
def execute(self,event=None):
self.function(self.widget,event,*self.parameters)
# GuiCallBack Functions for command and events
def commandCallBack(widget,function,parameters=()):
cmd = GuiCallback(widget,function,parameters)
widget.config(command=cmd.execute)
def eventCallBack(widget,eventkey,function,parameters=()):
cmd = GuiCallback(widget,function,parameters)
widget.bind(eventkey,cmd.execute)
# ================ Example from DynTkInter GuiCreator =======================
root = Tk()
root.title("GUI Callback Function")
master=LabelFrame(text="Place Layout")
master.pack(anchor='nw')
Label(master,text="x",padx='3').grid(column='1',row='0')
Label(master,text="y",padx='3').grid(column='1',row='1')
SpinboxX = Spinbox(master,increment='10.0',width='4',to='2000.0')
SpinboxX.grid(column='2',row='0')
SpinboxY = Spinbox(master,increment='10.0',width='4',to='2000.0')
SpinboxY.grid(column='2',row='1')
# ============ This is now the universal Callbackfunction =================
# Parameters me and event are required. The following parameter may be chosen free.
# These parameters are set, when the callbacks are defined (CallBack Functions commandCallBack, eventCallBack)
def function(me,event,xEntry,yEntry):
this().place(x=xEntry.get(),y=yEntry.get())
commandCallBack(SpinboxX,function,(SpinboxX,SpinboxY))
eventCallBack(SpinboxX,'<Return>',function,(SpinboxX,SpinboxY))
commandCallBack(SpinboxY,function,[SpinboxX,SpinboxY])
eventCallBack(SpinboxY,'<Return>',function,[SpinboxX,SpinboxY])
master = LabelFrame(text="PlaceArea",height='400',width='400')
master.pack()
SelectedWidget=Button(master,text="Let me move")
this().place(y='0',x='0')
root.mainloop()
Wenn man das weiss, kann man dann solchen Code in eine schöne Form bringen:
Code: Alles auswählen
# for Return key or mouse klick: get active selection from the listbox, hide the listbox, set the layout and insert the text in the Entry for showing
VAR["LISTBOXCLICK_Return"] = EvDataCmd("""
Data().execute() # getactive entry or nearest to mouse click
Par()[2].unbind("<Return>")
Par()[2].unbind("<Button-1>")
Par()[2].unlayout()
Par()[0].setconfig(Par()[1][0],top())
Par()[3].delete(0,END)
Par()[3].insert(0,pop())
""",EvCmd("push(Par()[2].get(ACTIVE))"))
VAR["LISTBOXCLICK_Mouse"] = EvDataCmd(VAR["LISTBOXCLICK_Return"],EvCmd("push(Par()[2].get(Par()[2].nearest(Event().y)))"))
Und dann ist mir noch etwas eingefallen. Hatte zuerst gedacht, dass man außer der ganzen Applikation nur ganze Container einzeln sichern und laden kann. Aber einzelne Widgets könnten auch noch Code nur für dieses Widget haben,
Etwa dass ein Label ein Blinklabel ist und man dann so etwas dazu importieren kann.
@Alfons Mittelmeyer: würdest Du konkrete Fragen stellen, könnte man Dir auch konkrete Antworten geben. Da ich aber nie genau weiß, worauf Du eigentlich hinaus willst, ist das schwierig. Da ich dann nur allgemeine Probleme ansprechen, die aber von Dir gerne ignoriert werden. Ich habe auch nie geschrieben, dass Du zwei Module aus Deinem einen machen sollst.
Ja, man kann alle Namen wiederverwenden und es ist egal, ob man an den selben Namen eine Zahl, eine Funktion oder ein Modul bindet. Ob man das tun sollte, ist aber eine andere Frage (wo hast Du irgendwann einmal erwähnt, dass Du damit ein Problem hast?). Python kann auch rechnen (2+4), falls Du das auch irgendwann mal brauchst, Dir aber noch niemand gesagt hat
.
Ein
geht in den aktuellen Python-Versionen noch nicht. Solch ein Konstrukt verwendest Du aber in Deinem gezeigten Code auch nicht. Die allgemeinste Lösung würde übrigens so aussehen:
Dann ist es egal welchen Typ args1 und args2 haben.
Nochmal persönlich, ich will Dich nicht runtermachen, sondern Dir helfen, Deine Coding-Skills zu verbessern. Dazu gehören ein paar allgemeine Spielregeln, die sich als nützlich erwiesen haben und gegen die Du konsequent verstößt (wenn Du nicht selbst herausfindest, warum, kann ich gerne noch außführliche Erklärungen zu jeden einzelnen Punkt geben):
Ja, man kann alle Namen wiederverwenden und es ist egal, ob man an den selben Namen eine Zahl, eine Funktion oder ein Modul bindet. Ob man das tun sollte, ist aber eine andere Frage (wo hast Du irgendwann einmal erwähnt, dass Du damit ein Problem hast?). Python kann auch rechnen (2+4), falls Du das auch irgendwann mal brauchst, Dir aber noch niemand gesagt hat

Ein
Code: Alles auswählen
function(*args1,me,event.*args2)
Code: Alles auswählen
function(*itertools.chain(args1, (me, event), args2))
Nochmal persönlich, ich will Dich nicht runtermachen, sondern Dir helfen, Deine Coding-Skills zu verbessern. Dazu gehören ein paar allgemeine Spielregeln, die sich als nützlich erwiesen haben und gegen die Du konsequent verstößt (wenn Du nicht selbst herausfindest, warum, kann ich gerne noch außführliche Erklärungen zu jeden einzelnen Punkt geben):
- eingerückt wird immer mit 4 Leerzeichen pro Ebene
- Namen von Variablen und Funktionen werden klein geschrieben, von Klassen groß, von Konstanten komplett in Großbuchstaben.
- Werte betreten eine Funktion als Argumente und verlassen sie als Rückgabewerte. Globale Variablen sind zu vermeiden.
- Sterchenimporte werden nicht benutzt
- eval, exec, compile, etc. werden nicht benutzt
- auf Modulebene stehen außer Konstanten- und Klassen und Funktionsdefinitionen kein Code
- jens
- Python-Forum Veteran
- Beiträge: 8502
- Registriert: Dienstag 10. August 2004, 09:40
- Wohnort: duisburg
- Kontaktdaten:
+1Sirius3 hat geschrieben:Zu jeder Regel gibt es Ausnahmen. Die müssen aber gründlich überlegt und begründet sein.
- eingerückt wird immer mit 4 Leerzeichen pro Ebene
- Namen von Variablen und Funktionen werden klein geschrieben, von Klassen groß, von Konstanten komplett in Großbuchstaben.
- Werte betreten eine Funktion als Argumente und verlassen sie als Rückgabewerte. Globale Variablen sind zu vermeiden.
- Sterchenimporte werden nicht benutzt
- eval, exec, compile, etc. werden nicht benutzt
- auf Modulebene stehen außer Konstanten- und Klassen und Funktionsdefinitionen kein Code
Das müßten wir mal in einer FAQ Packen!
Ach, schon erledigt: http://wiki.python-forum.de/FAQ#Was_sin ... en_Code.3F
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Sirius3 hat geschrieben: Eingeht in den aktuellen Python-Versionen noch nicht. Solch ein Konstrukt verwendest Du aber in Deinem gezeigten Code auch nicht. Die allgemeinste Lösung würde übrigens so aussehen:Code: Alles auswählen
function(*args1,me,event.*args2)
Dann ist es egal welchen Typ args1 und args2 haben.Code: Alles auswählen
function(*itertools.chain(args1, (me, event), args2))
Code: Alles auswählen
VAR["LISTBOXCLICK_Mouse"] = EvDataCmd(VAR["LISTBOXCLICK_Return"],EvCmd("push(Par()[2].get(Par()[2].nearest(Event().y)))"))
Und natürlich kommt das in meinem jetzigen Code auch nicht vor, weil ich die Parameter über die beiden Stacks hole anstatt sie zu übergeben.
Bei Funktionen sollen aber die Parameter übergeben werden.
Und da eine Kopie, also Instanzieren nur an dieser einen Stelle im Programm vorkommt, bzw. dasselbe nochmas beim Layoutmodul. Kann man auf eine Kopie verzichten.
Und eine Klasse dafür braucht man auch nicht - wenigstens für die normalen Funktionen - wenn man die Parameter mit Defaultwerten einfach hinten angibt, also:
Code: Alles auswählen
def function(par1,par2,par3,defPar1=DefValue1,defPar2=DefValue2)
Allerdings für die CallBacks mit *args muss man es noch überlegen. Denn da soll *args übergeben werden, die Parameter mit Defaultvalues sollen aber nicht angetastet werden.
Muss man wohl doch eine Klasse machen:
Code: Alles auswählen
def myfunction(defPar1,defPar2,par1,par2,par3): usw
MyFunction = Function(myfunction,(defValue1,defValue2))
MyFunction.call(val1,val2,val3)
Code: Alles auswählen
MyFunctionNew = Function(MyFunction,(defValueNew1,defValueNew2))
Also so eine Klasse braucht man auf jeden Fall, denn man will die Funktion laden können und später abfeuern, etwa über MessageQueue oder Timer:
Code: Alles auswählen
# statt MyFunction.call(val1,val2,val3)
MyFunction.par(val1,val2,val3)
# und später
MyFunction.execute()
Code: Alles auswählen
# mit tuple
MyFunction = Function(myfunction,(defValue1,defValue2))
# oder ohne tuple
MyFunction = Function(myfunction,defValue1,defValue2)
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Also zwei Parameter Teile für eine universelle Funktionsklasse genügen nicht. Ich komme auf folgende fünf Teile:
meself: Der Zugriff, des Funktionsobjektes auf sich selber. Damit erhält eine Funktion die Möglichkeit, auf Teile ihrer Parameter im Ganzen zuzugreifen.
Wenn etwa eine Funktion dafür da ist, einer anderen Funktion einen Teil der eigenen Parameter zu übergeben, braucht sie nicht explizit die Parameter einzeln zu übergeben, sondern kann gleich eine Liste von Parametern übermitteln.
Defaultmäßig ist dieser Parameter nicht vorhanden und muß bei der Initialisierung der Funktionsklasse entspechend angegeben werden, wenn man ihn haben will.
fixvalues: Das wird speziell benötigt, wenn Rückruffuntionen dynamisch Widgets anlegen und dafür Rückruffunktionen verwenden, sie sie als Parameter bekommen.
Wenn eine Rückruffunktion aufgerufen wird, weiß sie von lokalen Variablen zur Zeit ihrer Definition nichts mehr. Und der Rückruffunktion dann Parameter für Rückruffunktionen für von ihr dynamisch erstellte Widgets übergeben, kann man sich sparen, wenn die als Parameter übergebenen Rückruffunktionsobjekte gleich diese Werte enthalten.
parameters: das sind die eigentlichen Parameter der Funktion. Zwei Methohen existieren für die Übergabe, par und call:
additional: Das sind Parameter die von Callbacks gesetzt werden können. Das wäre aber nur der Parameter "mewidget".
Damit kann dann ein widget auf sich selber zugreifen. Defaultmäßig bekommt man den Parameter nicht, sondern muß bei den Callbacks angeben, ob man ihn will.
Eigentlich würde man ihn nicht brauchen, weil man ihn auch über par(..) übergeben könnte.
event: Braucht man oder auch nicht, je nachdem ob man es auswerten will. Wer es haben will, soll es bei der Callbackdefinition angeben.
message: kein zusätzlicher Parameter sondern identisch mit "event" sollte man in seiner Funktion am Besten aber nicht event nennen, wenn es stattdessen eine Message ist
Also, was meint Ihr zu diesen fünf Parameterteilen:
meself: Der Zugriff, des Funktionsobjektes auf sich selber. Damit erhält eine Funktion die Möglichkeit, auf Teile ihrer Parameter im Ganzen zuzugreifen.
Wenn etwa eine Funktion dafür da ist, einer anderen Funktion einen Teil der eigenen Parameter zu übergeben, braucht sie nicht explizit die Parameter einzeln zu übergeben, sondern kann gleich eine Liste von Parametern übermitteln.
Defaultmäßig ist dieser Parameter nicht vorhanden und muß bei der Initialisierung der Funktionsklasse entspechend angegeben werden, wenn man ihn haben will.
fixvalues: Das wird speziell benötigt, wenn Rückruffuntionen dynamisch Widgets anlegen und dafür Rückruffunktionen verwenden, sie sie als Parameter bekommen.
Wenn eine Rückruffunktion aufgerufen wird, weiß sie von lokalen Variablen zur Zeit ihrer Definition nichts mehr. Und der Rückruffunktion dann Parameter für Rückruffunktionen für von ihr dynamisch erstellte Widgets übergeben, kann man sich sparen, wenn die als Parameter übergebenen Rückruffunktionsobjekte gleich diese Werte enthalten.
Code: Alles auswählen
# Funktionsobjekt ohne meself und ohne fixvalues
MyFunction=Function(myfunction)
# Funktionsobjekt mit fixvalues und ohne meself
MyFunction=Function(myfunction,[value1.value2])
#oder
MyFunction=Function(myfunction,(value1.value2))
# Funktionsobjekt mit fixvalues und mit meself
MyFunction=Function(myfunction,[value1.value2],True)
#oder
MyFunction=Function(myfunction,(value1.value2),True)
# Funktionsobjekt ohne fixvalues und mit meself
MyFunction=Function(myfunction,None,True)
# oder
MyFunction=Function(myfunction,(),True)
# oder
MyFunction=Function(myfunction,[],True)
Code: Alles auswählen
# So wird ein Funktionsobjekt aufgerufen:
MyFunction.call(par1,par2,par3)
# So werden lediglich die Parameter geladen:
MyFunction.par(par1,par2,par3)
#Der Aufruf kann dann später mit MyFunction.execute() erfolgen
Damit kann dann ein widget auf sich selber zugreifen. Defaultmäßig bekommt man den Parameter nicht, sondern muß bei den Callbacks angeben, ob man ihn will.
Eigentlich würde man ihn nicht brauchen, weil man ihn auch über par(..) übergeben könnte.
event: Braucht man oder auch nicht, je nachdem ob man es auswerten will. Wer es haben will, soll es bei der Callbackdefinition angeben.
message: kein zusätzlicher Parameter sondern identisch mit "event" sollte man in seiner Funktion am Besten aber nicht event nennen, wenn es stattdessen eine Message ist
Also, was meint Ihr zu diesen fünf Parameterteilen:
- meself
fixvalues
parameters
additional (mewidget)
event (bzw. message)
Zuletzt geändert von Alfons Mittelmeyer am Donnerstag 6. August 2015, 16:57, insgesamt 1-mal geändert.
Ich habe keine Ahnung für was Funktionsobjekte sind, für was man Widget-Parameter oder Rückrufparameter braucht, oder wer was aufruft.
Kannst Du ein lauffähiges Beispiel posten, das wenigstens Teile Deiner Ideen demonstriert. Deine Vorgehensweise ist so weit weg von jeglichem Standard, dass es ohne konkreten Code wirklich schwierig ist, Dir zu sagen, wo Dein Denkfehler ist.
Kannst Du ein lauffähiges Beispiel posten, das wenigstens Teile Deiner Ideen demonstriert. Deine Vorgehensweise ist so weit weg von jeglichem Standard, dass es ohne konkreten Code wirklich schwierig ist, Dir zu sagen, wo Dein Denkfehler ist.
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Kann Dir leider kein einfaches Beispiel posten, denn so eine Klasse braucht man erst, wenn es nicht mehr einfach ist. Wenn etwa eine Rückruffunktion selber dynamisch Widgets installiert und dazu Rückruffunktionen über die Parameter zur Verfügung hat.Sirius3 hat geschrieben:Ich habe keine Ahnung für was Funktionsobjekte sind, für was man Widget-Parameter oder Rückrufparameter braucht, oder wer was aufruft.
Kannst Du ein lauffähiges Beispiel posten, das wenigstens Teile Deiner Ideen demonstriert. Deine Vorgehensweise ist so weit weg von jeglichem Standard, dass es ohne konkreten Code wirklich schwierig ist, Dir zu sagen, wo Dein Denkfehler ist.
Ein Beispiel: in meinem Config Modul ist eine Rückruffunktion für den Aufbau der GUI zur Änderung der Configparameter zuständig.
Zuerst löscht sie alles.
Dann installiert sie für jeden Config Eintrag einen Frame. In dem Frame ist ein Label und ein Entry zum Eingeben und für anchor und relief und so etwas werden Listboxen installiert, in denen der User etwas auswählen kann, wenn er das nicht selber alles schon weiß. Dazu wird in diesem Frame auch noch ein Button mit Rückruffunktion installiert. Wenn man diesen Button drückt, geht die Listbox auf und wenn man da etwas auswählt mittels Enter Taste oder Mausklick dafür gibt es auch eine Rückruffunktion.
Die Rückruffunktionen werden nicht erst definiert wenn der Callback ausgeführt wird, sondern schon vorher. Subroutinen werden auch nicht erst definiert, wenn der Callback ausgeführt wird. Und globale Variablen gibt es für diese Rückruffunktionen und Subroutinen natürlich nicht. Daher ist es gut, wenn in Subroutinen oder Callbacks bereits in den Parametern eine Referenz auf weitere Subroutinen und Callbacks steht. Und das sind diese fixvalues. Wenn man diese nicht braucht, dann geht es auch mit normalen Funktionen. Und normalerweise erzeugt man nicht dynamisch komplexe GUIs sodass sie mancher niemals braucht.
Den Rest ohne die fixvalues bekommt man auch mit normalen Funktionen, nein nicht ganz. Die Funktionen mit den Parametern laden und dann später ausführen lassen, geht bei normalen Funktionen natürlich auch nicht.
Allerdings ob ich die fixvalues wirklich brauche, weiß ich noch nicht. Meine Pseudofunktionen hatten keine Parameter. Wenn man aber in richtigen Funktionen Defaultwerte hat, könnte es auch ohne die fixvalues gehen. Aber das weiß ich erst, nachdem ich meinen Code umgeschrieben habe.
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Also, ich stimme Dir zu, dass man Funktionsobjekte nicht immer braucht. Sie wären zuzusagen die eierlegende Wollmilchsau. Man braucht sie, wenn man Funktionen erst später ausführen will, die man vorher mit Parameterwerten geladen hat. Aber was ist, wenn man keine veränderten Parameter braucht?
Bei einem Widget und einer Funktion braucht man es nicht. Bei vielen dynamisch erzeugten Widgets mit gleicher Funktion aber veränderten Parametern eventuell schon.
Bei Fumktionen die sofort ausgeführt werden, braucht man wohl auch nicht erst eine Funktionsklasse.
Und was sofort ausgeführt wird, sind Rückruffunktionen von Messages. Wenn Messages über die Queue oder direkt beim Empfänger ankommen, dann werden die Messages sofort ausgeführt und man schickt nicht zuvor den Empfänger noch über die Queue auf die Reise.
Also sollte man für Message Rüchruffunktionen implementieren, dass Funktionen direkt aufgerufen werden, anstatt sie vorher noch in eine Klasse zu konvertieren.
Das war bisher der Handler für Info Message Rückruffunktionen:
Der legt einfach die Daten in sich selber ab. Wenn die Message eintrifft (receive), legt der Handler sich selbst auf den Stack und ruft die entsprechende Rückruffunktion auf. Die kann dann über den Stack auf die Daten zugreifen.
Dabei wird zu Beginn unterschieden, ob es ein ausführbares Objekt ist. Wenn es ein String ist, wird dieser kompiliert und daraus ein ausführbares Objekt erzeugt. Das braucht man nun nur noch für den Fall einer Funktion ergänzen, der man dann bei receive schön die Parameter übergeben kann.
Ob das dann funktioniert, muss man testen:
Bei einem Widget und einer Funktion braucht man es nicht. Bei vielen dynamisch erzeugten Widgets mit gleicher Funktion aber veränderten Parametern eventuell schon.
Bei Fumktionen die sofort ausgeführt werden, braucht man wohl auch nicht erst eine Funktionsklasse.
Und was sofort ausgeführt wird, sind Rückruffunktionen von Messages. Wenn Messages über die Queue oder direkt beim Empfänger ankommen, dann werden die Messages sofort ausgeführt und man schickt nicht zuvor den Empfänger noch über die Queue auf die Reise.
Also sollte man für Message Rüchruffunktionen implementieren, dass Funktionen direkt aufgerufen werden, anstatt sie vorher noch in eine Klasse zu konvertieren.
Das war bisher der Handler für Info Message Rückruffunktionen:
Code: Alles auswählen
class DynAction:
def __init__(self,widget,cmd,data=None):
self.widget = widget
self.data = data
self.msgdata = None
if type(cmd) is str: self.evcode = EvCmd(cmd)
else: self.evcode = cmd
def receive(self,msgdata = None):
self.msgdata = msgdata
ObjectStack.append(self)
self.evcode.execute()
ObjectStack.pop()
Dabei wird zu Beginn unterschieden, ob es ein ausführbares Objekt ist. Wenn es ein String ist, wird dieser kompiliert und daraus ein ausführbares Objekt erzeugt. Das braucht man nun nur noch für den Fall einer Funktion ergänzen, der man dann bei receive schön die Parameter übergeben kann.
Ob das dann funktioniert, muss man testen:
Code: Alles auswählen
import types
class DynAction:
def __init__(self,widget,cmd,data=None):
self.widget = widget
self.data = data
self.msgdata = None
self.isfunction = False
if type(cmd) is types.TypeFunction:
self.isfunction = True
self.evcode = cmd
elif type(cmd) is str: self.evcode = EvCmd(cmd)
else: self.evcode = cmd
def receive(self,msgdata = None):
self.msgdata = msgdata
if self.isfunction:
if self.data == None: self.evcode(msgdata,self.widget)
else: self.evcode(msgdata,self.widget,*self.data)
else:
ObjectStack.append(self)
self.evcode.execute()
ObjectStack.pop()
DynAction kann man jetzt noch schön kürzen. cmd ist immer eine Funktion, in den Fällen, wo das bisher noch nicht der Fall ist, kannst Du ja den ohnehin schon vorhandenen Wrapper EvCmd gleich von außen übergeben. Aber ich glaube ja nicht, dass irgendein Anwender etwas anderes als eine Funktion übergeben will. Das Attribut msgdata wird nie benutzt, kann also weg. Was bleibt ist:
Code: Alles auswählen
class DynAction:
def __init__(self, widget, cmd, data=()):
self.widget = widget
self.data = data
self.evcode = cmd
def receive(self, msgdata=None):
self.evcode(msgdata, self.widget, *self.data)
@Alfons Mittelmeyer: Wenn Du möchtest, dass die Rückruffunktion (also das Callback) auf alle Attribute des aufrufenden Objektes zugreifen kann, dann lässt sich das verallgemeinert auch so schreiben:
Dann aber stellt sich die Frage, warum 'evcode' keine Methode ist, sondern ein Attribut, das auf ein externes Callable verweist, oder warum nicht direkt alles in 'receive' bearbeitet wird. Statt eine Funktion an DynAction zu übergeben, liesse sich auch eine Subklasse erstellen, die dann anstelle von DynAction, bzw. der ja auch irgendwo angelegten Rückruffunktion, verwendet wird.
Code: Alles auswählen
def receive(self, msgdata=None):
self.msgdata = msgdata
self.evcode(self)
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Eine extra Klasse für Funktionen brauche ich doch nicht. Ich habe meine vier Callbackklassen, die sich kaum unterscheiden, nämlich widget oder nicht widget, event oder nicht event, gleich ausführen oder später zu einer zusammengefasst und erweitert. Ich denke, damit kann man jetzt alles anstellen:
Code: Alles auswählen
from copy import copy
class Callback:
def __init__(self,function,parameters=None,widget=None,wishEvent=False,wishSelf = False):
self.widget = widget
self.event = None
self.parameters = []
if type(parameters) is tuple:
for e in parameters: self.parameters.append(e)
elif type(parameters) is list: self.parameters = parameters
elif parameters != None: print("Error: Callback parameters have to be tuples or lists")
self.wishEvent = wishEvent
self.wishSelf = wishSelf
self.function = function
self.mydata = None # may be used for many purposes. Accessible via self
# for execution later =======
def execute(self):
par = []
if self.widget != None: par = [self.widget]
if self.wishEvent: par.append(self.event)
if self.wishSelf: par.append(self)
par += self.parameters
return self.function(*par)
def setEvent(self,event = None):
self.event = event
return self.execute
# for execution immediate =======
def receive(self,event = None): return self.setEvent(event)()
# for using the Callback as funcion =======
def call(self,*args): return self.function(*args) # a function cannot be copied, but a Callback can. Using different mydata, the functions can behave different.
# Tests =========================================================================
# we need the parameters, if we want to use the same function for different widgets
def function(par1=1,par2=2,par3=3,fix="Fix"): print (par1,par2,par3,fix)
MyCallback = Callback(function)
MyCallback.receive()
MyCallback = Callback(function,("val1","val2","val3"))
MyCallback.receive()
# now with widget
def function(mewidget,par1=1,par2=2,par3=3,fix="Fix"): print (mewidget,par1,par2,par3,fix)
MyCallback = Callback(function,None,"Widget")
MyCallback.receive()
MyCallback = Callback(function,("val1","val2","val3"),"Widget")
MyCallback.receive()
# now with event
def function(mewidget, event, par1=1,par2=2,par3=3,fix="Fix"): print (mewidget,event,par1,par2,par3,fix)
MyCallback = Callback(function,None,"Widget",True)
MyCallback.receive("'Hello Event'")
def function(event, par1=1,par2=2,par3=3,fix="Fix"): print (event,par1,par2,par3,fix)
MyCallback = Callback(function,("val1","val2","val3"),None,True)
MyCallback.receive("'Hello Event'")
# now with self
def function(mewidget, event, meself, par1=1,par2=2,par3=3,fix="Fix"):
print (mewidget,event,par1,par2,par3,fix)
print("These are my parameters: ", meself.parameters)
MyCallback = Callback(function,("val1","val2","val3"),"Widget",True,True)
MyCallback.receive("'Hello Event'")
# now call and copy and mydata
def function(meself): meself.mydata()
MyCallback1 = Callback(function,None,None,False,True)
MyCallback1.mydata = lambda: print("I am Callback 1")
MyCallback2 = copy(MyCallback1)
MyCallback2.mydata = lambda: print("I am Callback 2")
MyCallback1.call(MyCallback1)
MyCallback2.call(MyCallback2)
@Alfons Mittelmeyer: Du willst also eine Callback-Instanz irgendeinem Event-Handler übergeben, der dann über receive(event) den Callback auslöst. Das ließe sich viel einfacher und flexibler mit einer lambda-Funktion erledigen.
Beispiel: aus
wird
Du siehst, Callback wurde einfach durch lambda ersetzt, die Funktionalität ist die selbe.
Beispiel: aus
Code: Alles auswählen
# now with event
def function(mewidget, event, par1=1,par2=2,par3=3,fix="Fix"): print (mewidget,event,par1,par2,par3,fix)
MyCallback = Callback(function,None,"Widget",True)
MyCallback.receive("'Hello Event'")
Code: Alles auswählen
def function(mewidget, event, par1=1,par2=2,par3=3,fix="Fix"): print (mewidget,event,par1,par2,par3,fix)
MyCallback = lambda event=None: function("Widget", event)
MyCallback("Hello Event")
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Ja sieht auf den ersten Blick so aus. Sagen wir die Funktionalität ist fast dieselbe. Muss überlegen, was flexibler ist und was den Anforderungen genügt.Sirius3 hat geschrieben:Du siehst, Callback wurde einfach durch lambda ersetzt, die Funktionalität ist die selbe.Code: Alles auswählen
def function(mewidget, event, par1=1,par2=2,par3=3,fix="Fix"): print (mewidget,event,par1,par2,par3,fix) MyCallback = lambda event=None: function("Widget", event) MyCallback("Hello Event")
-
- User
- Beiträge: 1715
- Registriert: Freitag 31. Juli 2015, 13:34
Hab nachgedacht. Läßt sich ganz bestimmt mit lambda machen. Die Frage ist nur, wie sähe das aus? Hier das Beispiel:Sirius3 hat geschrieben:Du siehst, Callback wurde einfach durch lambda ersetzt, die Funktionalität ist die selbe.Code: Alles auswählen
def function(mewidget, event, par1=1,par2=2,par3=3,fix="Fix"): print (mewidget,event,par1,par2,par3,fix) MyCallback = lambda event=None: function("Widget", event) MyCallback("Hello Event")
Code: Alles auswählen
# Alter Code ==================================================================
# for some layout options we offer a selection by listbox
VAR["LISTSELECTION"] = EvDataCmd("""
Button(text="?").rcgrid(0,2) # create a help button for showing the listbox
evcommand(Data(),(Msg(),top(),widget("Listbox"),widget("Entry")))
""",VAR["ListBoxHelpButton"])
VAR.pop("ListBoxHelpButton",None)
# So stelle ich mir den neuen Code vor ============================================================
def function(selectedElement,configoption,ListBoxHelpButtonCallbackFunction=VAR["ListBoxHelpButton"]):
Button(text="?").rcgrid(0,2) # create a help button for showing the listbox
evcommand(ListBoxHelpButtonCallbackFunction,(selectedElement,configoption,widget("Listbox"),widget("Entry")))
VAR.pop("ListBoxHelpButton",None)
VAR["LISTSELECTION"] = function
# Dazu soll nach meiner Vorstellung definiert sein ==================================================
# Aus DynTkInter widget class
def evcommand(self,function,parameters=None): self.config(command=lambda cmd=Callback(function,parameters,self).execute : send('execute_function',cmd))
# und dazu
def evcommand(evcmd,data=None): this().evcommand(evcmd,data)
Code: Alles auswählen
evcommand(ListBoxHelpButtonCallbackFunction,(selectedElement,configoption,widget("Listbox"),widget("Entry")))