Probleme mit Variablen und tkinter

Fragen zu Tkinter.
BlackJack

Die freien Namen im ``lambda``-Ausdruck werden erst beim Aufruf ausgewertet, dass heisst erst beim Aufruf wird geschaut an was die Namen `self` und `t` in diesem Augenblick gebunden sind. `self` bleibt ja immer gleich, nur `t` wird in der Schleife immer an verschiedene Texte gebunden und wenn die `command`-Funktion aufgerufen wird, ist das `t` an das letzte Element vom Schleifendurchlauf gebunden.

Default-Argumente von Funktionen werden dagegen immer bei der Definition an die Namen gebunden.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Danke BlackJack !!!

So nun habe ich mir gedacht auch die Tasten (+,-,etc) wiederholen sich in ihrer Funktion und so müsste es wieder mit lambda funktionieren. Nur geht es nicht - liegt es am syntax oder ist es ein denkfehler ?

Code: Alles auswählen

# -*- coding: iso-8859-15 -*-
from Tkinter import *

class Rechner (Frame):
      
      def __init__(self, master=None):
            tastenlayout = [("7",3,7,1,10,0),("8",3,7,2,0,0),("9",3,7,3,10,0),("4",3,8,1,0,10),
                            ("5",3,8,2,0,0),("6",3,8,3,0,0),("1",3,9,1,0,0),("2",3,9,2,0,0),
                            ("3",3,9,3,0,0),("0",3,10,1,0,10),(",",3,10,3,0,0)]
            
            funktastlayout = [("/",3,7,4,0,0),("X",3,8,4,0,0,),("-",3,9,4,0,0),("+",3,10,4,0,0)]

            speicher=""

            Frame.__init__(self, master,)
            self.pack()

            for text, width, row, column, padx, pady in tastenlayout:
                  
                  self.tasten = Button(self, text=text,width=width, command=lambda t=text: self.ausgabe.insert(50,t))
                  self.tasten.grid(row=row, column=column, padx=padx, pady=pady)

            for text, width, row, column, padx, pady in funktastlayout:

                  
                  self.funktasten = Button(self, text=text,width=width, command=lambda speicher=speicher+self.ausgabe.get()+ text : self.ausgabe.delete(0,END))
                  self.funktasten.grid(row=row, column=column, padx=padx, pady=pady)

                
            
            self.ausgabe = Entry(self,width=29,justify="right",)
            self.ausgabe.grid(row=0, column=0,columnspan=6,pady=10)

     
                  
root = Tk()
root.title('mudda`s Rechner 1.0')
root.config(cursor='crosshair')
Rechner(root).mainloop()
gruss und dank frank
BlackJack

Wie schon gesagt werden die Default-Argumente ausgewertet wenn die Funktion definiert wird. Also das was Du dort an `speicher` bindest, wird in der Schleife schon ausgewertet. Davon abgesehen das `self.ausgabe` zu dem Zeitpunkt noch nicht existiert, dürfte das nicht der richtige Zeitpunkt sein.

Da ``lambda``-Ausdrücke auf einfache Ausdrücke beschränkt sind, musst Du hier mit einem sogenannten "Closure" arbeiten. Ungetestet:

Code: Alles auswählen

    def _make_operator_command(self, text):
        def operator_command():
            self.speicher += self.ausgabe.get() + text
            self.ausgabe.delete(0, END)
        return operator_command
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Danke !!!

Also für Doofe: Mein Programm "hängt" beim der Ausführung in den beiden Schleifen und beim Tastendruck beginnt die "Auswertung" ....?! Da self.ausgabe.get () erst nach dem Drücken der Taste bekannt ist, kann ich es auswerten ?! Vorhergehend habe ich es mit def eingabeminus(self) ausgewertet und somit war self.ausgabe.get() bekannt ?! Dein Tip ist ja sowas in der Art nur nicht für alle einzeln, sondern für +,- etc. in einem Vorgang ? - alles klar ? Der Befehl return wird innerhalb eines Körpers einer Funktion verwendet -- habe ich abgeschrieben... Ich rufe beim Drücken der Tasten die Funktion def operator_command auf und diese gibt mir die werte in die Schleife zurück ...?. Bei dem vorhergenden Programm habe ich das nicht gemacht - ging aber doch auch ?

gruss und dank frank
BlackJack

Ehrlich gesagt habe ich Deinen letzten Text nicht verstanden. :-(

Das `command`-Argument von `Button` will eine aufrufbare Funktion/Methode haben. Die Methode `_make_operator_command()` hat als Rückgabewert eine Funktion, nämlich eine Instanz von der inneren Funktion `operator_command()`. Diese kann man an das `command`-Argument eines Buttons binden. Die Funktion wird genau dann aufgerufen wenn man auf den Button klickt.

Innere Funktionen haben Zugriff auf die lokalen Namen des Aufrufs der umgebenen Funktion, dass heisst wenn man so einen Namen benutzt, bekommt man das Objekt an den der äussere Name gerade gebunden ist. Wenn man eine Instanz der inneren Funktion als Rückgabewert benutzt, nimmt diese den lokalen Namensraum des äusseren Aufrufs sozusagen mit.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Das geht mir bei Deinen Erklärungen auch so (soll nicht wertend sein, sondern mir fehlen die Fachbegriffe oder das nötige Hintergrundwissen) Ich komme aus der Sonderpädagogik, deswegen lerne ich immer über das Handeln z.B.: Man lernt das Autofahren indem man ein Auto fährt und nicht weil man es in der Theorie kann. Immer beides zusammen, deswegen auch meine "doofen" Fragen. In dem Wort begreifen steckt das Wort greifen. Erst mit dem Greifen nach etwas kann man es begreifen - hört sich gut an - blöde Pädagogen :-).

Trotzdem vielen Dank für Deine schnellen Antworten und ich versuche es weiter mal mit Lesen.

gruss und dank frank
sape
User
Beiträge: 1157
Registriert: Sonntag 3. September 2006, 12:52

Stichwort: Closures

Ein par Beispiel zum letzen Post von BlackJack gibt es hier: [wiki]Closures richtig anwenden[/wiki]

Und eine ausführlich Erklärung hier: http://de.wikipedia.org/wiki/Closure
BlackJack

@kaytec: "Closures" sind auch ein klein wenig komplizierter. Aber wenn man sie erst einmal verstanden hat, dann lassen sie sich vor allem in der GUI-Programmierung verwenden um Callback-Funktionen zu erstellen, die komplexere Dinge anstellen können, als mit ``lambda`` möglich ist.

Zum Thema ausprobieren: Dazu eignet sich Python durch den interaktiven Interpretierer sehr gut. Man kann "live" mit den ganzen Objekten herumspielen. Ein weiteres beliebtes "Closure"-Beispiel ist eine Funktion, die Funktionen produziert, welche einen festen Wert zu einem Argument addieren:

Code: Alles auswählen

In [46]: def make_adder(number):
   ....:     def add(n):
   ....:         return number + n
   ....:     return add
   ....:

In [47]: add5 = make_adder(5)

In [48]: add5(0)
Out[48]: 5

In [49]: add5(10)
Out[49]: 15

In [50]: add5(42)
Out[50]: 47
pyStyler
User
Beiträge: 311
Registriert: Montag 12. Juni 2006, 14:24

hi,
hier mal dein Code wie es ein bisschen optimiert funktionieren könnte.
Dabei kannst auch gleich die unschlagbaren kombination von Tkinters layout Manager sehen.

link: http://paste.pocoo.org/show/1159/

Gruss
pyStyler
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo pystyler !!

Ja so einen gibt es schon im Forum, doch bei meinem wollte ich nicht, dass die Zeichen +,- etc. im Rechenvorgang zusehen sind, sondern es wie ein richtiger taschenrechener funktioniert.

gruss und dank frank
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

kaytec hat geschrieben:doch bei meinem wollte ich nicht, dass die Zeichen +,- etc. im Rechenvorgang zusehen sind, sondern es wie ein richtiger taschenrechener funktioniert.
Mein "richtiger Taschenrechner" Zeigt auch Rechenzeichen an - finde ich sehr praktisch, weil ich die Eingaben editieren kann.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Hallo pystyler !!

Da hast Du recht - habe ja auch ihn gefunden bei meiner Suche nach einem Anstoss, doch ihn kopieren wäre ja nicht gerade eine Anforderung - code zu klauen ist ok, doch wollte ich es halt anders machen und dabei lernen. Es geht nicht um besser oder schlechter, sondern um den Spass an der Sache (intrinsiche Motivation).

Danke für Deine Links - gruss frank
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

So - habe es mal eingebaut! Es passiert nicht´s - bekomme aber auch keine Fehlermeldung. Das Entry - Feld wird auch nicht gelöscht ??

Code: Alles auswählen

 self.funktasten = Button(self, text=text,width=width, command=lambda text=text:self._make_operator_command)
                  self.funktasten.grid(row=row, column=column, padx=padx, pady=pady)

                
            
            self.ausgabe = Entry(self,width=29,justify="right",)
            self.ausgabe.grid(row=0, column=0,columnspan=6,pady=10)


              
      def _make_operator_command(self, text):
            
              def operator_command():
                  self.speicher += self.ausgabe.get() + text
                  self.ausgabe.delete(0, END)
              return operator_command

gruss und dank frank
BlackJack

Erst einmal brauchst Du natürlich das ``lambda`` nicht mehr. Die Funktion soll ja von der neuen Methode erzeugt werden. Und dazu musst Du die auch aufrufen ─ mit dem Text der eingefügt werden soll als Argument.

Code: Alles auswählen

taste = Button(..., command=self._make_operator_command(text))
Der Name `taste` braucht auch nicht an das `Rechner`-Objekt gebunden werden. Ausserdem ist Mehrzahl falsch, der Name ist ja immer nur an *einen* `Button` gebunden. In der Zifferntasten-Schleife ist es das gleiche.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Danke BlackJack !!!

Habe voher mal versucht die links über closures zu verstehen - ok hat nicht geklappt !!! :-) Durch Deine Hilfe geht es jetzt - super und danke

gruss frank
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

So an den Rest habe ich auch mal versucht einzubauen, doch macht das so Sinn oder geht es noch einfacher ?

Code: Alles auswählen

def make_auswertung(self,text):

            def auswerten():

                  if text =="C":
                        
                        self.ausgabe.delete(0,END)
                        self.speicher = ""
                        self.mspeicher = ""

                  if text =="%":

                        self.speicher = self.ausgabe.get()
                        self.speicher = (self.speicher + "/" + "100" + "*")
                        self.ausgabe.delete(0,END)

                  if text =="M":

                        if self.ausgabe.get() =="":
                  
                              self.speicher = self.speicher + self.mspeicher
                  
                        if self.ausgabe.get() !="":
                  
                              self.mspeicher = self.ausgabe.get()
                              self.ausgabe.delete(0,END)
                              self.ausgabe.insert(50,"")


                  if text =="+/-":

                        self.vorzeichen = str(float (self.ausgabe.get()) * -1)
                        self.ausgabe.delete(0,END)
                        self.ausgabe.insert(50,self.vorzeichen)
                  
                  if text =="=":
            
                        try:
                  
                              self.speicher = str(eval (self.speicher + self.ausgabe.get()))
                              self.ausgabe.delete(0,END)
                              self.ausgabe.insert(50,self.speicher)
                              self.speicher = ""

                        except:
                              
                              self.ausgabe.delete(0,END)
                              self.speicher = ""
                              self.ausgabe.insert(50,"E")
               

            return auswerten
gruss und dank frank
BlackJack

Das "verstecken" der Alternativen in einer inneren Funktion und die ganzen ``if``-Abfragen finde ich persönlich unschön. Eine Funktion sollte möglichst nur eine abgegrenzte Funktionalität implementieren. `auswerten()` macht dagegen eine ganze Menge verschiedener Dinge.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Danke BlackJack !!!

Jede Taste ausformulieren, damit ich an jede einzelne Taste eine Funktion binden kann ? So wie der Rechner am Anfang war ?

gruss frank
BlackJack

So würde ich das machen, ja. Wobei ich wohl auch den Taschenrechner, d.h. die Logik davon, von der GUI trennen würde. Damit man den Taschenrechner selbst, ohne GUI (automatisch) testen kann und eventuell auch die GUI austauschbar ist.
Benutzeravatar
kaytec
User
Beiträge: 608
Registriert: Dienstag 13. Februar 2007, 21:57

Danke BlackJack !!!

Mir wurde bei einem anderem Programm gesagt:
"Hi kaytec!
Auf den ersten Blick würde ich sagen, dein Programm ist nicht kompliziert, sondern eher etwas zu ausformuliert. Du könntest mit einigen Schleifen sicher recht viel code sparen, da tw. eigentlich immer das selbe machst und nur die Namen austauscht. Zudem kann ich dir für solche Sachen Tix.LabelEntry empfehlen, dann ist's noch kürzer. "

Jetzt habe ich dieses Programm mit schleifen verkürzt - oder habe ich das nicht richtig verstanden?

Das wichtigste ist doch das Auslesen des string mit eval(self.speicher). Der Rest ist doch eigentlich nur für die Eingaben über die Oberfläche und das richtige Einlesen in den string ? Meinst du damit die Trennung von Oberfläche und Berechnung ?

gruss frank
Antworten