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