Help for Tkinter and python's programming
Russell E. Owen
owen at nojunk.invalid
Sun Jun 2 00:57:26 EDT 2002
In article <mailman.1022985123.22656.python-list at python.org>,
aurel-le-gnou <aurelienviolet at noos.fr> wrote:
>...
>button_go.bind("<Button-1>", make_call(frame_left, frame_right,
>list_one, list_two, button_number, button_search, button_language,
>button_rank, button_engine))
># the arguments of make_call() are visible and defined in the main when
>I do this "bind".
This is a common problem with callbacks in Python. When you supply
make_call(frame_left...) as a callback argument, you are actually
executing the function at that very moment and passing the result of its
execution to button_go.bind. In other words, you aren't passing a
function, you are passing a result of calling a function. Thus nothing
at all happens later when you press the button.
You must pass in the name of a function WITHOUT the parenthesis, e.g.
make_call, not make_call(frame_left...). But there is a hitch.
button_go.bind requires a callback function that takes exactly one
argument: an event. Yet you want to pass in a lot of extra information.
What you want is some way of saying "here is a function with some stuff
I know now and some other stuff I am going to specify later".
The thing I usually do in this situation is to create a callable object.
This is handy because an object can hold state, i.e. the "stuff I know
now", yet can also have a __call__ method which allows you to call the
object just like a function.
This comes up often enough that I have a class GenericCallback which
makes it easy (see code at the end, with thanks to Eric Brunel). Here is
an example, a simplified version of your code:
Suppose I want to call a function from a button press event, and that
function has two pieces of info I know in advance: frame_left and
frame_right. A callback function bound to a button press event must take
exactly one argument: the event (which in your case you don't care
about, but you still have to accept it!).
First define a function that takes all arguments, starting with the ones
you know in advance (frame_left and frame_right) and ending with the
ones that will be sent to the callback (evt):
def myFunc(frame_left, frame_right, evt):
...your code here...
Now create a GenericCallback object that can be called by bind. It must
take just one argument (evt), so you specify all the other arguments to
GenericCallback. This code is complete:
myCallback = GenericCallback(myFunc, frame_left, frame_right)
button_go.bind("<Button-1>", myCallback)
That's all there is to it! I've appended my GenericCallback.py. I save
it as a file and import it when I need it, and I use it a lot. There are
other approaches. Perhaps if I understood Python's scoping rules better
I could use non-local variables in the function, but I find the rules
non-obvious (whether the variable is local or global depends critically
on how it first appears in the function--on the right or left of an
assignment) and prefer to make things more explicit. Some folks use
lambdas, but I personally find them ugly.
-- Russell
Here is GenericCallback.py:
"""From Scott David Daniels
<http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549>.
My changes include:
- Used a name suggested by Eric Brunel
- Created a simpler and faster class that does not handle initial
keyword arguments
- Created a factory function to return the fastest callable object.
"""
def GenericCallback(callback, *firstArgs, **firstKWArgs):
"""Returns a callable object that when called with (*args, **kwArgs),
calls a callback function with arguments: (*(firstArgs + args),
**allKWArgs),
where allKWArgs = firstKWArgs updated with kwArgs
Note that if a given keyword argument is specified both at
instantiation
(in firstKWArgs) and when the object is called (in kwArgs),
the value in the call (kwArgs) is used. No warning is issued.
"""
if firstKWArgs:
return GC(callback, *firstArgs, **firstKWArgs)
else:
return GCNoKWArgs(callback, *firstArgs)
class GC:
"""A generic callback class."""
def __init__(self, callback, *firstArgs, **firstKWArgs):
self.__callback = callback
self.__firstArgs = firstArgs
self.__firstKWArgs = firstKWArgs
def __call__(self, *lastArgs, **kwArgs):
if kwArgs:
netKWArgs = self.__firstKWArgs.copy()
netKWArgs.update(self.__kwArgs)
else:
netKWArgs = self.__firstKWArgs
return self.__callback (*(self.__firstArgs + lastArgs),
**netKWArgs)
class GCNoKWArgs:
"""A generic callback class optimized for the case
of no stored keyword arguments. This has the potential
to significantly speed up execution of the callback.
"""
def __init__(self, callback, *firstArgs):
self.__callback = callback
self.__firstArgs = firstArgs
def __call__(self, *args, **kwArgs):
return self.__callback (*(self.__firstArgs + args), **kwArgs)
More information about the Python-list
mailing list