[Tutor] Tkinter wierdness
Jeff Shannon
jeff@ccvcorp.com
Tue Dec 10 15:29:03 2002
Isaac Hall wrote:
>P.S. the inevitable follow-up question: why must this be done? if I wish
>to call a function with some arguments, how is this done?
>
The reason this must be done is based on Python reference semantics.
When you construct a button, you're setting the command to be a
function that you want called when the button is pressed. This means
that when you have 'command=xxx', you want xxx to be a *reference* to a
function. Tkinter then holds on to that reference and calls it as
necessary. In Python, when you use a bare function name, it evaluates
to a reference to that function, but when you put parens after it, that
function is immediately called.
To look at this a bit more concretely, say you have a function,
MyFunc(), which you want to have called when a button is pressed. If
you say "command=MyFunc", then a reference to your function is passed
into Tkinter (and given the name command). Now, at some later point,
Tkinter can execute "command()" and it will be identical to Tkinter
having executed "MyFunc()" at that point.
On the other hand, if you say "command=MyFunc()", then MyFunc() is
executed *right then*, when your button is created, and the *results* of
that are assigned to command. Since most handlers in GUI toolkits are
supposed to have no return value (they return None), you've essentially
told Tkinter that the command to execute when the button is pressed, is
None -- so of course it does nothing.
As far as passing arguments to a command function -- that's a little
tricky, because Tkinter won't call command() with any arguments, so you
need to find some way to wedge your data in there without requiring it
to be a normal parameter. This technique is called currying, and
there's *lots* of discussions about it in the comp.lang.python archives.
One of the most common methods is to use a lambda to create an
anonymous function that requires no arguments, whose effect (when
called) is to call the *real* function with an argument that's specified
when you create the lambda. (Personally, I never quite got the hang of
lambdas and don't particularly like them, so I can't demonstrate the
proper way to do this -- I can never remember proper lambda syntax. I'm
sure others will chime in, though.)
Another way of doing this is to create a nested function, which has much
the same effect as the lambda, but gives the intermediate function a
name. This would look something like this:
# create a button to call MyFunc with the current value of MyName
def namecommand():
return MyFunc(name=MyName)
self.button = Button(root, command=namecommand, ...)
Another, still more flexible option, is to use a callable class instance
instead of a regular function for your command.
class namecommand:
def __init__(self, name):
self.name = name
def __call__(self):
# do stuff making use of self.name
# ... when creating the button...
MyFunc = namecommand(MyName)
self.button = Button(root, command=MyFunc, ...)
You can pass any number of parameters to the class __init__(), of
course, and anything you set as an object attribute will be available
when the object is called. The only requirement is that __call__() has
no parameters other than "self", because Tkinter will call it with no
parameters. (The "self" will automagically come from Python's object
reference semantics.) You can even change the parameters later by
modifying MyFunc.name, if you wish.
Jeff Shannon
Technician/Programmer
Credit International