[Tutor] My best GUI app so far.

Jeff Shannon jeff at ccvcorp.com
Tue Jan 11 21:20:50 CET 2005


Jacob S. wrote:

> Great! I took the improvements you gave me an added support for keys (So you
> can type in 1.25+2= instead of having to type the buttons.) As always, I
> encourage improvements to my code. Maybe that will be my disclaimer... I
> have always liked and wanted to adopt Liam's.

Here's a few thoughts....  :)



>      def equal(self,*args):
>          if self.action:
>              self.newnum = self.distext.get()
>              self.newnum = str(eval(self.oldnum+self.action+self.newnum))
>              self.distext.set(self.newnum)
>              self.oldnum = '0'
>              self.action = ''
>              self.shouldblank = True


Instead of using text representations of the operators, and eval()ing 
a string, why not use the operator module?

Your operator keys can set self.action to be operator.add, 
operator.subtract, etc; then your equal() function becomes

     def equal(self, *args):
         if self.action:
             self.newnum = self.distext.get()
             self.newnum= str(self.action(float(self.oldnum), \
                                     float(self.newnum)))
             self.distext.set(self.newnum)
             self.oldnum = '0'
             self.action = ''  # I'd actually prefer None here...
             self.shouldblank = True

> 
>      def add(self,*args):
>          self.handleOperator('+')

becomes

     def add(self, *args):
         self.handleOperator(operator.add)

The handleOperator() function can stay the same.


>      def clear(self):
>          self.action = ''
>          self.oldnum = '0'
>          self.distext.set('0')
>          self.shouldblank = True

As I mentioned in a comment above, I'd prefer to use None for 
self.action when it's unset.  There's no real practical difference, 
but conceptually it's more accurate to use a null-object than to use 
an empty string.  Minor style point, I know, but you *did* ask for 
advice. ;)

>      def memminus(self):
>          self.memory = str(eval(self.memory+"-"+self.distext.get()))
>          self.shouldblank = True
> 
>      def memplus(self):
>          self.memory = str(eval(self.memory+"+"+self.distext.get()))
>          self.shouldblank = True

Why use eval() here?  You could just as easily do these as

     def memminus(self):
         self.memory = str(float(self.memory) - \
                            float(self.distext.get()))
         self.shouldblank = True

I try to avoid using eval() wherever possible, which is almost 
everywhere. ;)  There's a huge security hole in using eval() on 
arbitrary strings, and it's not the greatest performance either. 
(Each invocation of eval() will generate bytecode and create a code 
object that does essentially the same thing as my explicit conversion 
code does, so by doing it manually you save a parsing/compiling step.) 
  You're not eval()ing arbitrary strings here, at least, but it's 
still good practice to only use eval() when absolutely necessary.


>      def __init__(self, master=None):
>          Frame.__init__(self,master)
>          self.master.title("Calculator by Jacob, Inc.")
>          self.pack(expand=True)
>          m = lambda x: self.adddigit(x)
>          self.bl = [lambda *x: self.adddigit('0',x),
>                     lambda *x: self.adddigit('1',x),
>                     lambda *x: self.adddigit('2',x),
>                     lambda *x: self.adddigit('3',x),
>                     lambda *x: self.adddigit('4',x),
>                     lambda *x: self.adddigit('5',x),
>                     lambda *x: self.adddigit('6',x),
>                     lambda *x: self.adddigit('7',x),
>                     lambda *x: self.adddigit('8',x),
>                     lambda *x: self.adddigit('9',x)]
>          for y in range(10):
>              self.bind_all(str(y),self.bl[y])
>          self.bind_all("+",lambda x: self.add(x))
>          self.bind_all("-",lambda x: self.subtract(x))
>          self.bind_all("*",lambda x: self.multiply(x))
>          self.bind_all("/",lambda x: self.divide(x))
>          self.bind_all("=",lambda x: self.equal(x))
>          self.bind_all(".",lambda x: self.adddigitdot(x))

There's absolutely no point to doing lambda x: somefunc(x) -- all 
you're doing is adding an extra layer of function call.  You can 
replace all of those with something like

            self.bind_all('+', self.add)

And actually, because I'm not fond of lambda to begin with, I'd 
redefine your adddigit() method:

         def make_adddigit_callback(self, digit):
             def adddigit(*args):
                 self.ctb()
                 self.distext.set(self.distext.get()+digit)
             return adddigit

(You may not need the *args above, if the button callback is expected 
to be zero parameters -- I don't use Tkinter, so I'm not sure 
offhand.)  Then your button bindings can simply be

         self.bl = [ self.make_adddigit_callback('0'),
                     self.make_adddigit_callback('1'),
                     self.make_adddigit_callback('2'),
                     ... ]

Or even --

     self.bl = [self.make_adddigit_callback(digit) \
                            for digit in '0123456789']

Remember, there's nothing particularly special about lambdas -- you 
can create and pass around regular named functions, too.  Each time 
that make_adddigit_callback() is called, it creates and returns a new 
function object which captures the current value of 'digit', in 
exactly the same way that lambda does.  A function object (whether 
named with def, or lambda) like this, which captures a variable's 
current state, is called a closure.  Closures are indispensible for 
GUI callbacks like this, and many people automatically turn to lambda 
when they want a closure.  For me, though, having a proper def 
statement somewhere feels clearer.  (The merits of lambda vs. def'd 
functions are a frequent subject of heated debate on comp.lang.python, 
so if you prefer to stick with the lambdas in this case, I'm sure 
you'd be able to find plenty of people to support you... ;) )

Jeff Shannon
Technician/Programmer
Credit International




More information about the Tutor mailing list