[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