[Tutor] My best GUI app so far.

Jacob S. keridee at jayco.net
Tue Jan 11 21:48:54 CET 2005


> > 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

I know why people make money programming... ;-)

I'm not adverse to any conventional way of getting the project to work.
There are only a few exceptions like the proposal in python3 to remove
raw_input(). You would have to import sys to get sys.stdin.readlines(). Just
odd, quirky, whys.
I digress.


I think the make a new function idea will work. The only reason I used
lambdas is because Kent suggested them when I pointed out that you can't
call the functions when defining them in the buttons, so you can't pass
parameters. Or so I thought. Yes, I think your idea will work. What am I
talking about? Of course your idea will work. ;-)

Thanks,
Jacob Schmidt



More information about the Tutor mailing list