'command' zwei Argumente übergeben?

Fragen zu Tkinter.
BlackJack

@Alfons Mittelmeyer: Nein, es ist nicht klar wozu man ein erweitertes ``lambda`` *braucht*, man braucht es nämlich nicht, man kann einfach eine Funktion definieren. Funktionsdefinitionen sind nicht nur dazu da um von anderen Funktionen benutzt zu werden, sondern auch um den Code zu strukturieren und das Programm dem Leser verständlich zu machen. Dabei helfen *Namen* ungemein, insofern haben benannte Funktionen einen *Vorteil* gegenüber anonymen Funktionen. Insbesondere sobald eine Funktion nicht mehr nur aus einem einfachen Ausdruck besteht dem man sofort ansieht was er tut.

Man muss auch keine Klasse schreiben „[d]amit Funktionen nicht miteinander ins Gehege kommen“. Klassen schreibt man weil mehere Funktionen sich Zustand teilen. Tun sie das nicht, braucht man auch keine Klasse zu schreiben. Das mit dem Speicherverbrauch solltest Du bitte erst einmal belegen *und* dass das tatsächlich ein Problem ist. Deine Zeichenketten mit Code belegen auch Speicher, vielleicht sogar mehr als der compilierte Code der wieder freigegeben werden kann. Die Programmierung wird auch nicht umständlich, das ist sie eher bei Deinem komischen, unübersichtlichem Stapelkonzept und der ganzen Magie. Du bist das anscheinend so gewohnt und denkst das muss so, ist aber nicht so. ;-)

Zu welchem Problem dieses `pseudofunction()`-Beispiel eine Lösung sein soll, erschliesst sich mir nicht ganz.

Du willst auf jeden Fall ganz eindeutig nicht in Python programmieren.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Stimmt, mit Klassendifinitionen kann man sich auch nicht gegen Überschreibungen schützen.

Aber ein Problem ist auch: ich lade eine GUI Seite in einen Frame und auch eine andere GUI Seite in einen anderen Frame.
Und dummerweise werden dort dieselben Funktions- oder Klassennamen benutzt und dann gibt es einen Crash.

Wenn es keine Funktionen und Klassen gibt, gibt es deswegen keinen Crash!

Übrigens habe ich das Problem gelöst. Kompilierung zur Laufzeit brauche ich doch nicht. Mit lambda habe ich auch Variablen, kann auch zusätzliche loke Variablen definieren und Logik geht auch.
Hier sieht man schon einmal if und else:

Code: Alles auswählen

def pseudofunction(par=None):pass

def if_execute(condition, function):
	if condition: function()


def ifelse_execute(condition, function1, function2):
	if condition: function1()
	else: function2()


a=(lambda a=5, b=6,c=7: pseudofunction((
print(a),
print(b),
print("noch etwas"),
print(c),
ifelse_execute(a<b,lambda: print("a < b"),lambda: print("a >= b"))
)))

a()

print("\nEinmal gemacht\n")

a()

print("\nZweimal gemacht")
BlackJack

@Alfons Mittelmeyer: Ich sehe gerade Deinen Crash nicht. Klar kann man das so schreiben das es Probleme gibt, man kann die aber auch vermeiden.

Deine Problemlösung zeigt einmal mehr das Du kein Python verwenden willst. Es gibt Sprachen die so etwas wie ”lazy”-Ausdrücke unterstützen (Io, D, …) und die dazu geeignet sind sich eigene Kontrollstrukturen als Funktionen/Methoden zu schreiben — Python gehört nicht dazu, da muss man dann alles in zusätzliche Funktionen verpacken, was die Sache hässlich macht.
Benutzeravatar
kbr
User
Beiträge: 1508
Registriert: Mittwoch 15. Oktober 2008, 09:27

@Alfons Mittelmeyer: Python hat ein eigenes Paradigma: die "pythonische" Programmierung. Du bist mit einem ganz anderen Paradigma unterwegs. So wird das nichts.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

kbr hat geschrieben:@Alfons Mittelmeyer: Python hat ein eigenes Paradigma: die "pythonische" Programmierung. Du bist mit einem ganz anderen Paradigma unterwegs. So wird das nichts.
Was hat das mit einem Paradigma zu tun? Python ist gut hat aber zwei Schwachstellen. Es geht hier um Beseitigung von GUI Callbacks. Wenn das Widget gelöscht wird soll auch der Callback beseitigt wirden. Wird er ja auch. Nur die Schwachstelle ist, dass man dafür mit lambda nur einen einzigen Ausdruck angeben kann. Die Programmiersprache Squirrel etwa bietet dasselbe als Funktion an. Und genau das wird benötigt.

Wenn man lambda benützt, muiss man Klimmzüge machen mit eigenen Kontrollstrukturen. Meinst Du daß ich darauf versessen bin und so etwas gerne tue? Im Gegenteil.

Die andere Lösing ist zur Laufzeit kompilierter Code und da ist die andere Schwachstelle, der hat nämlich keine Parameterübergabe und definierte Variablen sind dann global. Und da muss man wieder Klimmzüge machen.

Aber mit lambda ist es wohl besser als der kompilierte Code. Ich stelle dann wohl den so um, dass man die Objekte nicht mehr mit cmd.execute() zur Ausführung bringt, sondern mit cmd() und dann lambda benützen kann.
Da braucht man dann auch keine Klimmzüge mehr zu machen, um dann etwas ähliches wie Parameter zu übergeben. Die compilierten Objecte musste ich erst auf einen extra Stack puhen, damit sie Zugriff auf Parameter erhielten:

Hier ist etwa die Definition für den Command:

Code: Alles auswählen

def evcommand(self,evcmd,data=None):  self.config(command=(lambda cmd=DynCommand(self,evcmd,data) : send('execute_message',cmd)))
Das Kommando evcmd kann ein String sein oder ein ausführbares Objekt evcmd.execute()
Das wird dann weiter verpackt. damit es auch die Parameter bekommt und noch anderes. Zur Ausführungszeit wird zur Serialisierung der command nicht gleich ausgeführt sondern über die Message Queue an einen Empfänger geschickt, der ihn dann ausführt.

DynCommand erzeugt ein Objekt mit der Referenz auf das Widget sowie mit den übergebenen Daten, ich hatte sie Parameter genannt - vielleich etwas unglücklich gewählt diese Benennung.
Und wenn es ein String war, wird er compiliert. Zur Ausführungszeit, muß dann das Objekt erst noch auf einen Stapel gehievt werden, denn der compilierte Code kommt sonst nicht an die Daten (widget und parameter heran)

Code: Alles auswählen

class DynCommand:
	def __init__(self,widget,cmd,data=None):
		self.widget = widget		
		if type(cmd) is str: self.evcode = EvCmd(cmd)
		else: self.evcode = cmd
		self.data = data

	def execute(self):
		ObjectStack.append(self)
		self.evcode.execute()
		ObjectStack.pop()
Für den Zugriff auf das oberste Element hingeben gibt es dann Funktionen:

Code: Alles auswählen

def receiver(): return ObjectStack[-1]
def Par(): return receiver().data
def Me(): return receiver().widget
Wäre natürlich schön, wenn solche Klimmzüge nicht nötig wären, ich meine damit, ohne Funktionsdefinierung. Dass das ganz einfach mit def geht, weiß ich selber, muß mir keiner sagen.

Wenn man jetzt allerdings mit lambda arbeiten würden, dann könnte man schön die Parameter übergeben und bräuchte dann das Hieven auf den Stack nicht.
BlackJack

@Alfons Mittelmeyer: Python ist eine objektorientierte, imperative Programmiersprache und es gibt ein paar Grundsätze wie „pythonischer” Code auszusehen hat. Und Deiner ist sehr weit davon entfernt als „pythonisch” durchzugehen.

Das was Du als Schwachstellen siehst sind keine. Du siehst Probleme wo keine sind, und schreibst Code ”gegen” die Sprache, statt die Sprache so zu verwenden wie sie von den Erfindern gedacht ist.

Wenn Du auf die Klimmzüge bei ``lambda`` und Kontrollstrukturen nicht versessen bist und so etwas nicht gerne tust, warum *tust* Du es dann? Warum versuchst Du Dein Paradigma mit aller Gewalt in die Sprache zu pressen? Sowohl bei ``lambda`` als auch bei zur Laufzeit kompiliertem Code siehst Du doch selber das Du Klimmzüge machen musst, warum siehst Du das nicht als Zeichen das Du da etwas machst was so nicht vorgesehen ist?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Python ist eine objektorientierte, imperative Programmiersprache und es gibt ein paar Grundsätze wie „pythonischer” Code auszusehen hat. Und Deiner ist sehr weit davon entfernt als „pythonisch” durchzugehen.
Als was der bei Dir gilt, ist mit egal. Mir geht es nur darum, dass wenn ein Widget gelöscht wird, dann auch nichts mehr über bleibt. Alles muss weg sein! Ist das so schwer zu verstehen?
BlackJack hat geschrieben:Das was Du als Schwachstellen siehst sind keine. Du siehst Probleme wo keine sind, und schreibst Code ”gegen” die Sprache, statt die Sprache so zu verwenden wie sie von den Erfindern gedacht ist.
Ich verwende die Sprache genau so, wie sie vorgesehen ist. Und vorgesehen ist, dass sie zur Laufzeit kompileren kann. Wenn Du nicht einverstanden bist, dass sie das kann, dann streite darüber mit Guido van Rossum. Aber werfe mir bitte nicht vor, dass ich verwende, was die Sprache bietet.
BlackJack hat geschrieben:Wenn Du auf die Klimmzüge bei ``lambda`` und Kontrollstrukturen nicht versessen bist und so etwas nicht gerne tust, warum *tust* Du es dann?
Bei lambda hatte ich geschrieben, dass es gehen würde, auch mehrzeiligen Code und gar mit Kontrollstrukturen zu schreiben, Und das hatte ich auch gezeigt. Aber das sieht mir so haarsträubend aus, dass ich so etwas nicht tue.
Dein Vorwurf, "Warum tust Du es dann?" ist also völlig an den Haaren herbeigezogen.
BlackJack hat geschrieben:Warum versuchst Du Dein Paradigma mit aller Gewalt in die Sprache zu pressen? Sowohl bei ``lambda`` als auch bei zur Laufzeit kompiliertem Code siehst Du doch selber das Du Klimmzüge machen musst, warum siehst Du das nicht als Zeichen das Du da etwas machst was so nicht vorgesehen ist?
Wie bereits vorher geschrieben, bei lambda mache ich gar nichts. Lambda würde sehr viele Probleme lösen, wenn es nicht nur eine Expression, sondern eine ganze Funktion wäre. So ist lambda leider nicht besonders hilfreich.
Und wenn man keinen haarstäubenden lambda code schreiben möchte mit eigenen Kontrollstrukturen und wenn man will, dass auch mehrzeiliger Code weg ist, wenn die Referenz darauf weg ist, bleibt eben nur die Kompilierung.
Und die Kompilierung zur Laufzeit ist vorgesehen und bietet genau was ich will, nämlich mehrzeiigen Code ohne haarsträbende Kontrollstrukturen. Leider bietet sie keine lokalen Variablen, sondern alle Variablen wären dann global. Lokale Variablen hätte ich gerne, bekomme ich leider nicht. Und globale Variablen sollte man meiden. Sollte eigentlich jeder Programmierer wissen. Und ohne lokale und globale Variablen wird die Programmierunhg schwierig. Bleibt nur mehr so etwas wie ein Stack.
Muss ich jetzt Deiner Meinung nach globale Variablen benützen, weil die Sprache keine lokalen vorsieht? Und ist die Benützung eines Stacks als Ersatz für lokale Variablen und zur Vermeidung globaler Variablen gegen die Sprache? Und etwas, was man daher nicht tun darf?
Also ich weiß nicht was Du willst? Ich benütze ganz genau, was die Sprache vorsieht, behelfe mich mit einem Stack, den die Sprache auch vorsieht, als Ersatz für lokale Variablen, welche die Sprache leider nicht vorsieht und zur Vermeidung globaler Variablen.

Und ich will nicht in Python programmieren? Genau in Python will ich programmieren. Weiß Du was ich tun könnte? die Objekte mit der Methode .execute() sind nicht auf kompilierten Python Code festgelegt. Sie könnten auch eine Sequenz von solchen Objekten sein mit Kontrollstrukturen. Darüber könnte ich auch ein FORTH System einbinden, das in Python geschrieben ist. Das wäre auch nicht gegen die Sprache. Aber da ich in Python programmieren will, tue ich das nicht. Python kann man auch mit C erweitern. Darüber könnte ich gar eine weitere Programmiersprache wie Squirrel einbinden. Und das tue ich auch nicht, da ich der Ansicht bin, dass es Python sein soll und keine andere Sprache.

Aber auch das sieht Python vor und wäre daher also auch nicht gegen die Sprache!

Und ich denke, ein grafischer GUI Creator, der in Python geschrieben ist, ist wohl besser für den Anwender als ein Konsolen GUI Creator in FORTH , den ich zuvor hatte und der auch in Python geschrieben ist. In Python kann man eine GUI auch über die Konsole erstellen. Aber wenn man etwas tun will, muß man eine Funktion im Python Stil eingeben mit mehr Tipparbeit. In Python ist es 'ls()', um sich das Directory und die Auswahl zeigen zu lassen, in FORTH war es nur 'l' und 'goIn()' war in FORTH auch nur '\' und um eine widget anzuwählen, braucht es ein 'goto("name")', in Forth war es aber nur 'name'

Das wäre mit FORTH zwar GUI gewesen, aber nicht Python. Ich habe starke Zweifel, ob das einer gewollt hätte.

Aber mit unserer Diiskussion sind wir schon lange am Thread Thema vorbei. The Thema war: command mit zwei Argumenten. Und das Ergebnis war, dass beliebig viele Argumente möglich sind und gar noch beliebig viele Funktionsaudrufe. Sogar noch Kontrollstrukturen sind möglich. Das war dann das gewesen:

Code: Alles auswählen

def pseudofunction(par=None):pass
 
def if_execute(condition, function):
        if condition: function()
 
 
def ifelse_execute(condition, function1, function2):
        if condition: function1()
        else: function2()
 
 
a=(lambda a=5, b=6,c=7: pseudofunction((
print(a),
print(b),
print("noch etwas"),
print(c),
ifelse_execute(a<b,lambda: print("a < b"),lambda: print("a >= b"))
)))
 
a()
 
print("\nEinmal gemacht\n")
 
a()
 
print("\nZweimal gemacht")
Und damit wär dieses Thema wohl zuende.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Das Thema ist doch nicht ganz zuende, denn def kann man nehemen, und es stört nicht gegenseitig. HIer zwei Callbackfunktionen aus dem PlaceLayout:

Code: Alles auswählen

# receiver for external or internal BASE_LAYOUT_REFRESH message
# set the bg color of Label PlaceTitle to yellow, of the widget has a place layout
# and enable or disable buttons MouseOn, MouseOff and Adjust
registerReceiver('BASE_LAYOUT_REFRESH',"""

def function(title = Par()[0],bg = Par()[1],MouseOn=Par()[2],MouseOff=Par()[3],Adjust=Par()[4]): 

	if this().Layout == PLACELAYOUT:
		title['bg'] = 'yellow'	
		state = 'normal'
	else:
		title['bg'] = bg
		state = 'disabled'

	MouseOn['state'] = state
	MouseOff['state'] = state
	Adjust['state'] = state

function()
""",(widget("PlaceTitle"),widget("PlaceTitle").getconfig("bg"), widget("MouseOn"),widget("MouseOff"),widget("Adjust")))

# set X,Y spinboxes to X,Y layout values of the widget
registerReceiver('POSITION_CHANGED',"""
if this().Layout==PLACELAYOUT:

	def function(SpinboxX = Par()[0],SpinboxY = Par()[1]):
		SpinboxX.delete(0,END)
		SpinboxX.insert(0,this().getlayout("x"))
		SpinboxY.delete(0,END)
		SpinboxY.insert(0,this().getlayout("y"))

	function()
""",(widget("X"),widget("Y")))
Wenn man immer dieselbe Funktion nimmt, dann ist es nur eine. Und weil man sie immer neu definiert, bevor man sie aufruft, kommt auch nichts ins Gehege.
Und längeren Code kann man dann auch schön untergliedern. Einach abschnittsweise function definieren und dann aufrufen.
Und bei Unterabschnitten geht es dann genauso. Das wären dann lokale Funktionsdefinitionen.
Sirius3
User
Beiträge: 18294
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: zeig zuerst einmal, dass es ein Problem gibt, wenn Code, im Arbeitsspeicher verbleibt, der nicht mehr benötigt wird. Dann zeige, dass Deine Klimmzüge schonender mit dem Arbeitsspeicher umgehen.
Bei Strings hast Du sowohl den String als auch den compilierten Code im Speicher. Deine seltsamen Par und Stack-Konstrukte brauchen ein vielfaches an Speicher gegenüber normalen lokalen Variablen.

Wie misst Du den Arbeitsspeicherverbrauch? Was ist Dein Referenz? Hast Du ein Testprogramm in beiden Varianten geschrieben um den direkten Vergleich zu haben?
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ich bin ganz bei Blackjack...

Der code sieht grausig aus.
Probleme zu lösen, deren Existenz noch nicht bewiesen ist, macht doch keinen Sinn...

Wie hieß das noch: Premature optimization is the root of all evil

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@Alfons Mittelmeyer: Du verwendest die Sprache nicht so wie sie vorgesehen ist. Nur das man etwas machen kann, heisst nicht das man es auch so machen sollte. Es gibt das Zen of Python, und mit dem ist Dein Vorgehen so gar nicht konform. Dein Code gilt nicht nur bei mir als „unpythonisch”. Das kann Dir natürlich egal sein, aber ich frage mich dann was Du überhaupt erreichen willst‽ Kein Python-Programmierer wird Deinen Programmierstil übernehmen. Das ist ein krasser Ausreisser gegenüber der üblichen, offensichtlichen Vorgehensweise, und das bei einer Programmiersprache wo die Community Wert darauf legt das es für viele Probleme *eine* *offensichtliche* Lösung geben sollte, auch wenn man natürlich fast alles auch irgendwie anders lösen könnte.

Die Sprache sieht sehr wohl lokale Variablen vor: In Funktionen. Benutze die einfach und Deine Phantomprobleme verschwinden.

Am Thema sind wie seit Deinen Beiträgen vorbei, denn die haben keinen praktischen Nutzen was die Ausgangsfrage betrifft. Die ist mit `partial()` hinreichend und „pythonisch“ beantwortet gewesen.

Du willst nicht in Python programmieren. Du willst die Sprache und deren Syntax benutzen, aber idiomatischen Python-Code willst Du ganz offensichtlich nicht schreiben. Tipparbeit sparen zu wollen auf Kosten der Lesbarkeit ist da auch so ein Punkt.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Braucht mir keiner sagen, dass es grausig ist, wenn man im Callback eine Funktion definiert, die man dann ausführt. Das geht auf die Laufzeit. Eine Funktion sollte bereits definiert sein und nicht erst definiert werden.

Aber ich glaube, ich weiß jetzt wie es geht. Von Moderatoren und anderen Usern mit Erfahrung würde ich ja so etwas wie einen Tipp erwarten anstatt nur immer Kritik.
Ich denke, dass es mit partial geht und ohne Kompilierung zur Laufzeit. Das müßte man einmal ausprobieren.

Und es geht! Da brauch ich immer nur denselben Funktionsnamen:

Code: Alles auswählen

from tkinter import *

root = Tk()

a = Button(text="Button1")
a.pack()

ISay = "Hallo"
def function(say=ISay): print(say)
a.config(command=function)


a = Button(text="Button2")
a.pack()

ISay = "Echo"
def function(say=ISay):
	print(say)
	print("Und ich sag noch etwas extra")
a.config(command=function)

root.mainloop()
Und wenn ich dann die Funktion nicht gleich ausführen will, sondern über die Messageqeue oder auch um ein paar Sekunden verzögert, dann brauch ich die send Funktion mit zwei Parametern.
Dafür könnte man partial nehmen. Aber partial ist so etwas von unnötig. Denn es reicht, die Funktion mit mehr Parametern einfach einer anderen Funktion, die nichts macht, als Argument zu übergeben.

Jetzt habe ich es wohl kapiert. Naja, wenn man C/C++ gewohnt ist, stellt man sich eben vor, dass es hier zur Laufzeit auf den Namen der Funktion geht, und denkt derselbe Name bedeutet dieselbe Funktion.

Naja, von Compilersprachen her ist man gewohnt, die Variablen als variabel anzusehen und die Funktionen als fix. Aber hier bei Python sind Funktionennamen praktisch nicht die eigentlichen Funktionen sondern Variablen, welche dann die eigentliche Funktion referenzieren. Diese Variablen ändert man dann nicht mit "=" sondern mit "def"

Das war mir nicht bewußt gewesen, und deshalb habe ich dann wirklich den GUI Creator gegen die Sprache geschrieben. Da ist dann wohl ein umfassendes Refactoring angesagt.

Dumm, wenn man diese variable Funktion verzweifelt sucht und nicht draufkommt, dass es sie ja gibt, dass man sozusagen den Wald vor lauter Bäumen nicht sieht.

Dann biete ich nicht mehr evcomand an, sondern sendcommand, wenn jemand die Rückruffunktion über die Message Queue versenden will. Messages mit Delay (mms) machen aber bei Baodcast Messages wohl wenig Sinn. Bei Messages an bestimmte Empfänger gibts es sie. Indem ein Label sich selber verzögert immer wieder eine Message schickt und dann den Messageinhalt (True,False) umswitched, kann er etwa blinken. Aber ich will die Möglichkeiten auch nicht beschneiden und biete Delayed vielleicht dann auch bei Broadcast Messages an.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Habe jetzt die Callback Funktionen optimal an normale Funktionen angepaßt.
Eine variable Anzahl von Parametern und deren Übergabe bekommt man optimal über eine GuiCallback-Klasse.

Habe Euch dazu ein komplettes Beispiel erstellt:

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=None):
		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 parameters 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))
commandCallBack(SpinboxY,function,(SpinboxX,SpinboxY))
eventCallBack(SpinboxX,'<Return>',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()
Na, was meint Ihr dazu?


Für keine Parameter braucht es noch eine Korrektur, nämlich eine leeres tuple für parameters

Code: Alles auswählen

def commandCallBack(widget,function,parameters=()):
def eventCallBack(widget,eventkey,function,parameters=()):
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Das sieht schon mehr nach "normale" Python code aus :D

Ganz allgemein:
Sternchen-Importe meiden.
Die Einrückung sieht nicht nach üblichen 4-Leerzeichen aus, oder?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Sirius3
User
Beiträge: 18294
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: Du bist immer noch nicht ganz von Deinem Over-Engineering weg, aber schon ein ganz schönes Stück näher an Python heran. Globale Variablen sind schwer zu warten und machen mehr Probleme als sie scheinbar lösen. Bei Deinem Beispiel ist "global" überflüssig, weil es nur gebraucht wird, wenn der Wert einer globalen Variable verändert wird. this ist überflüssig, weil man überall wo this aufgerufen wird auch gleich SelectedWidget schreiben könnte. Das setzen von SelectedWidget auf None ist überflüssig, weil None ein genauso gut oder schlecht ist, wie eine undefinierte Variable. Die GuiCallback-Klasse ist überflüssig, weil sie nichts mehr kann als ein einfacherer lambda-Ausdruck:

Code: Alles auswählen

def commandCallBack(widget,function,parameters):
    cmd = lambda event=None: function(widget, event, *parameters)
    widget.config(command=cmd)
Mir ist auch nicht ganz klar, was der Vorteil wäre, dafür eine extra Funktion zu schreiben.
Auf Modulebene sollte außer Definitionen kein Programmcode stehen.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

ach, und funktions-namen -> pep8 :D

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: Du bist immer noch nicht ganz von Deinem Over-Engineering weg, aber schon ein ganz schönes Stück näher an Python heran. Globale Variablen sind schwer zu warten und machen mehr Probleme als sie scheinbar lösen. Bei Deinem Beispiel ist "global" überflüssig, weil es nur gebraucht wird, wenn der Wert einer globalen Variable verändert wird. this ist überflüssig, weil man überall wo this aufgerufen wird auch gleich SelectedWidget schreiben könnte.
Also gerade das this() ist global und im Orignal handelt es sich bei this() um Selection.widget. Normalerweise befindet sich der Programmierer vor dem Computer und nicht im Programm. Selection ist sozusagen der Ort des Avatars des Programmierers in der GUI. Mittels der Selection kann der Programmierer sich nach Belieben durch die GUI bewegen und die GUI ändern wo er will und das während des laufenden Programmes.

Zumindest, wenn er meine Erweiterung DynTkInter benützt - noch nicht veröffentlicht - anstatt tkinter. Ob ich beim Namen Selection bleibe oder bei Selection.widget, weiß ich noch nicht, ob er mir gefällt. Bei this() allerdings möchte ich bleiben. Und die Selection als Ort des Avatars des Programmiers in der GUI muß immer gegeben sein. Also ob ich global auch ein paarmal geschrieben hatte, wo man das nicht braucht. muss ich mal untersuchen.
Sirius3 hat geschrieben:Das setzen von SelectedWidget auf None ist überflüssig, weil None ein genauso gut oder schlecht ist, wie eine undefinierte Variable.
Irgendwo im Programm muß ja mal die Variable definiert werden. Und das in einem Abschnitt wo man solche Definitionen übersichtlich zusammenfasst. Und wenn das irgendwann zufällig während des Programmlaufes erst geschieht, dann trägt das sicherlich nicht zur Übersicht über die Definitionen bei. Oder kann man auch eine Variable ohne Wertzuweisung definieren? Mir kommt es nur darauf an, dass sie an einer Stelle im Code, wo ich die Definitionen machen will, steht. Oder gefällt Dir None nicht, soll ich lieber "Wrdbrmpfd" schreiben?

Außerdem ist die Selection in meinem Originalprogramm selbstversändilich definiert mit Selection.container = root und Selection.widget = root.
Sirius3 hat geschrieben:Die GuiCallback-Klasse ist überflüssig, weil sie nichts mehr kann als ein einfacherer lambda-Ausdruck
Da ist die Frage, wie das mit lambda ist. BlackJack hatte geschrieben, dass man lambda vermeiden sollte, weil der Guido von Rossum überlegt uder überlegt hatte, ob er es rauswerfen will. Jetzt weiß ich nicht, ob ich jetzt lambda benutzen soll oder nicht.
Dann ist natürlich die GuiCallback Klasse ganz und gar nicht überflüssig, Sie wäre überflüssig, wenn mein Code so wäre wie im gezeigten Beispiel. Ich führe die Callbacks nicht gleich aus, weil ich gemerkt hatte, dass tkinter zumindest bei mir in Linux nicht so streng sequentiell läuft wie andere Guis unter Windows. Da ist es vorgekommen, dass während etwas anderes lief, ein event dazwischen kam und dadurch etwas verstellt worden war.

Daher versende ich das CallBack Objekt über eine MessageQueue an einen Empfänger, der das CallBack Objekt dann bekommt, wenn es in der Queue an der Reihe ist und es dann ausführt.
Bisher habe ich das GuiCallback Objekt verschickt und dann wird es mit Object.execute() ausgeführt.

Jetzt wäre die Frage, obe es nicht besser wäre, das etwas anders zu handhaben. Wenn ich Object.execute versenden würde, käme es als 'function' beim Empfänger an und würde mit function() ausgeführt werden.
Dann könnte man sonstige parameterlose Funktionen auch über diese Weise über die Queue ausführen lassen.
Und auch nicht nur solche Objekte verzögert nach millisekunden ausführen lassen, sondern auch Funktionen. Wäre vielleicht besser, auf Basis von Funktionen zu gehen, anstatt auf die von ausführbaren Objekten?
Sirius3 hat geschrieben:Mir ist auch nicht ganz klar, was der Vorteil wäre, dafür eine extra Funktion zu schreiben.
Das sollte jeder Programmierer wissen, was der Vorteil ist. Wenn ich mich entscheide, etwas zu ändern zum Beispiel statt gleich ausführen über die MessageQueue, dann brauch ich das nur für die zwei Callback Funktionen ändern und nicht bei hundert Stellen im Programm. Genau dafür schreibt man Funktionen.
Sirius3 hat geschrieben:Auf Modulebene sollte außer Definitionen kein Programmcode stehen.
Sorry, dass ich jetzt für das Beispiel alles funktionierend in einem Modul zusammengefasst hatte. Das sollte doch jeder sich denken können, dass das was am Anfang steht, normalerweise in einem anderen Modul steht.
Sirius3
User
Beiträge: 18294
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: jetzt wird mir einiges klar: DynTkInter ist Deine eigene Erfindung und damit mußt Du natürlich Dein Konzept mit allen Mitteln verteidigen. TkInter ist nicht perfekt in der Bedienung, weil es schon mehr als 30 Jahre alt ist. Daher bist Du nicht der erste, der da noch einen Aufsatz draufsetzt. Hast Du Dir schon andere GUI-Kits angeschaut um aus deren Ideen und Fehlern zu lernen?

Generell ist es schlecht, globale Variablen zu haben, weil sich solch ein Programm weder warten noch testen läßt. lambdas sind inzwischen sogar in Python3 werden also nicht mehr rausfliegen. Sie haben ihren Einsatzzweck und der ist hier gegeben. Funktionen sind in Python 1st-class Objekte, ein Klassenwrapper wie in Java ist damit, wie Du ja selbst erkannt hast, unnötig.

Ob SelectedWidget den Wert None oder "Wrdbrmpfd" hat, beides führt zu einem Attribute-Error wenn es uninitialisiert benutzt wird. Läßt Du es ganz weg, bekommst Du einen NameError, der Dir immerhin sagt, dass Du vergessen hast, SelectedWidget einen sinnvollen Wert zu geben. Anders ist es natürlich, wenn Du an jeder Stelle, an der Du SelectedWidget einsetzt prüfst, ob SelectedWidget None ist, was Du aber nicht machst.

Wenn TkInter nicht "streng sequentiell" läuft, wie soll das eine MessageQueue verhindern? Und ob alles in einem Modul ist oder nicht, hab ich gar nicht gemeint, sondern dass Du außer Definitionen nichts auf oberster Ebene haben solltest, weil sich solche Programme nicht nebenwirkungsfrei importieren lassen, um sie z.B. zu testen. Außerdem werden so globale Variablen erzeugt, die man nicht haben möchte.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben: Generell ist es schlecht, globale Variablen zu haben, weil sich solch ein Programm weder warten noch testen läßt. lambdas sind inzwischen sogar in Python3 werden also nicht mehr rausfliegen. Sie haben ihren Einsatzzweck und der ist hier gegeben. Funktionen sind in Python 1st-class Objekte, ein Klassenwrapper wie in Java ist damit, wie Du ja selbst erkannt hast, unnötig.

Ob SelectedWidget den Wert None oder "Wrdbrmpfd" hat, beides führt zu einem Attribute-Error wenn es uninitialisiert benutzt wird. Läßt Du es ganz weg, bekommst Du einen NameError, der Dir immerhin sagt, dass Du vergessen hast, SelectedWidget einen sinnvollen Wert zu geben. Anders ist es natürlich, wenn Du an jeder Stelle, an der Du SelectedWidget einsetzt prüfst, ob SelectedWidget None ist, was Du aber nicht machst.
Sorry, dass ich mir für das Beispiel nicht soviel Mühe gegeben hatte. Zweck war die Darstellung der CallBack Funktionen und nicht die Darstellung der Initialisierung.

Initialisiert wird alles beim Aufruf von Tk(). Und deswegen ist egal ob wrdlbrpfd oder sonst etwas da drin steht. Man hätte auch diese Variblen gar nicht schreiben brauchen, weil sie ja sowieso beim Aufruf von Tk() initialisiert werden. Ich kabe mir nur gdacht, wenn ich sie auch so in die Source schreibe, dann weiß ich dass ich sie habe.

Oder hätte ich sie vielleicht auskommentieren sollen?

Und was globale Variablen betrifft, die ich bei fast 2000 Zeilen DynTkInter und fast 3000 Zeilen (Kommentarzeilen und Leerzeilen mitgezählt) verwendet habe, dann ist das hier alles:

Code: Alles auswählen

_Application = None # das ist die root. Wird angelegt bei root = Tk(). Aber wegen dieser Variablen kann man einfach nur Tk() schreiben.
		    # Wird benützt für mainloop() ohne root Angabe


_Selection=Create_Selection()       # Der Ort an dem man sich in der GUI befindet
_AppRoot = Create_Selection()       # die Root als Selektion. Der Startpunkt und um dahin wieder auf einmal zurückzugelangen
_TopLevelRoot = Create_Selection()  # Eine Art Superroot. Ich lasse Toplevel Windows nicht in dem Verzeichnis anzeigen, in denen sie erstellt wurden.
                                    # Stattdessen werden die Applikation und Toplevel Windows in dieser Toproot angezeigt. Dient zum Überwechseln
                                    # von der GUI der Applikation in die von Toplevel Windows

_AppConf = None			    # Konfiguration des Main Windows zu Beginn. Es sollen nur Veränderungen gegenüber der Anfangskonfiguration gespeichert werden

Stack = []			    # Dient zur Vermeidung sonstiger globaler Variablen. Wenn jemand etwas falsch macht. merkt er das schon

VAR = {}			    # Dient ebenfalls zur Vermeidung globaler Variablen. Wird hautsächlich nur bei der Definition der GUI verwendet
				    # Und die Werte werden nach Gebrauch wieder beseitigt. Wäre zu überlegen, ob man das eventuell nur lokal macht,
				    # sodass ein lokales Dictionary bei Löschen eines Container widgets wieder verschwindet, sofern der User doch etwas nicht gelöscht hat.
				    # Wenn es sich nicht um tausend GUI Seiten handelt, in denen etwas vergessen wurde, wäre wohl global kein Problem.
				    # Jetzt hätte ich die Idee, dass man vielleicht automatisch alles löscht, wenn mainloop() aufgerufen wird.
				    # Oder auch nach import von Modulen. Überlege ich mir noch.
				    # Es ist eigentlich nur für die GUI Erstellung gedacht also zwischen Tk() und mainloop() oder während Modulimport

ObjectStack = [] # Zugriff auf Parameter von Rückruffunktion, zur Zeit schwer vermeidbar. Komplexe Rückruffunktionen
		 # bedienen sich auch Helferfunktionen für Teilaufgaben. Zur Zeit der Ausführung eines Callbacks werden
		 # durch diesen Stack die Parameter des Callbacks temporär global und Helferfunktionen können darauf zugreifen.
		 # Wenn man die Helferfunktionen allerdings jeweils mit den benötigten Parametern aufrufen würde, wäre der Stack 
		 # vermeidbar. Allerdings wäre da viel umzustellen
		 #
		 # Eine Schwierigkeit ist dabei eine Klasse, die bereits eigene Parameter hat.
                 #
		 # Dabei handelt es sich um Funktionen der Form
                 #
		 # def function(*args)
		 # 
		 # Und beim Aufruf der Objekte bekommen die Funktionen ihre Parameter übergeben.
		 # Die benütze ich auch manchmal so, dass ich Kopien mit anderen Parameterwerten anlege.
		 # Und die habe ich gelegentlich zusätzlich zu ihren eigenen Parametern auch auf die Parameter des sie aufrufenen Callbacks zugreifen lassen
                 #
                 # Ist mir jetzt unklar, wie ich das jetzt anders machen könnte, denn zweimal *args geht nicht.


SelfStack = []  # Zugriff von Objekten (nicht widgedts, sondern etwa die zuvor genannte Klasse) auf sich selber.
		# Dürfte vermeidbar sein, wenn man echte Klassen mit Funktionen, benützt.
		# Aber jeweils eine eigene Klasse definieren, wenn man da auch Funktionen und Daten reinkopieren kann, bzw. ohne oder nach Kopieren des Objekts
		# austauschen kann?

ACTORS = {}      # Hier werden einfach nur die Referenzen von widgets eingetragen, die Empfänger für info messages sind.
		 # Dient zur Absturzvermeidung. Beim Senden einer info nachricht an ein widget, wird überprüft, ob das widget dort eingetragen ist.
		 # Wenn nicht, wird die Nachricht nicht weitergeleitet. Wenn ein widget gelöscht wird mit widget.destroy()
		 # wird dessen Eintrag automatisch aus dieser Liste entfernt

Proxy = None     # Dient zur applikationsweiten Nachrichtenübertragung. Gibt unabhängig voneinander agierenden Modulen die Möglichkeit miteinander zu kommunizieren

NOLAYOUT = 0     # Layoutkennung für das Layout der Widgets
PACKLAYOUT = 1
GRIDLAYOUT = 2
PLACELAYOUT = 4
LAYOUTNEVER = 8
Da habe ich noch etwas vergessen. Die Variable 'var' benütze ich noch für Checkboxen und Radiobuttons.
Sirius3 hat geschrieben:Wenn TkInter nicht "streng sequentiell" läuft, wie soll das eine MessageQueue verhindern?
Die laufenden Programme beinhalten nach Definition lediglich Callbacks, nämlich GUI Callbacks und Message Callbacks.
Und wenn die alle über die Message Queue laufen, dann kommt die Behandlung eines anderen Events erst dann dran, wenn der Callback des vorherigen vollständig ausgeführt wurde. Also kann sch da nichts überschneiden.
Sirius3 hat geschrieben:Und ob alles in einem Modul ist oder nicht, hab ich gar nicht gemeint, sondern dass Du außer Definitionen nichts auf oberster Ebene haben solltest, weil sich solche Programme nicht nebenwirkungsfrei importieren lassen, um sie z.B. zu testen. Außerdem werden so globale Variablen erzeugt, die man nicht haben möchte.
Wie gesagt sorry, dass ich das in einem Beispiel zusammengefasst hatte. In meinen GUI Modulen des GuiCreators ist außer Definitionen gar nichts. Ich hatte mich sogar gegen die Verwendung von Funktionsdefinitionen gesträubt und Variable hatte ich für DynTkInter GUI Module auch keine benützt außer Stack, VAR[] und var.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Noch ein zweiter Knackpunkt für das Refactoring. Also wie wir bereits festgestellt hatten, ist das eine geeignete Form für eine allgemeine CallBack Funktion:

Code: Alles auswählen

function(me,event,*args)
Jetzt gibt es aber Funktionen, die als Templates für Rückruffunktionen dienen. Diese Funktionen haben allgemein diese Form:

Code: Alles auswählen

function(*args)
Bei der Definition dieser Funktionen erhalten sie Parameterwerte, die zur Zeit ihrer Definition bekannt sind.
Solche Funktionen werden an komplexe Rückruffunktioinen übergeben, welche dynamisch Widgets erzeugen und dazu diese Funktionen als Ruckruffunktionen benutzen.
Beim Aufruf als Callback sollen dann diese Funktionen folgendermassen aussehen:

Code: Alles auswählen

function(*args1,me,event,*args2)
Außerdem soll man Kopien dieser Funktionen anlegen können mit ausgetauschtem Teil *args1

Ein Beispiel: es gibt eine Rückruffunktion für die Auswahl aus einer Listbox mittels Enter Taste.
Dieselbe Funktion ließe sich auch fast benützen für die Auswahl durch Mausklick. Der Unterschied wäre lediglich die erste Zeile.
Anstatt die erste Zeile in den Code zu schreiben, kann man sie als lambda expression auch als Parameter für args1 übergeben bei der Funktionsdefinition.
Wenn man nun die Funktion kopiert, aber nicht manuell, sondern eine neue Instanz erzeugt, mit geänderter lambda expression hat man auch die Rückruffunktion für den Mausklick.
Gesperrt