4

I am trying to figure out exactly how to implement a callback function which does something more meaningful than just print output. I am fairly inexperienced, so I am not sure how callback functions should or can be implemented in Python (or in any other language, for that matter).

Consider the following Python code:

from Tkinter import *

def callbackfunc(*args):
  print "Hello World!"

class App:

  def __init__(self, master):
    frame = Frame(master)
    frame.pack()

    optionvalue = IntVar(master)
    optionvalue.set(2)
    optionvalue.trace("w", callbackfunc)
    self.optionmenu = OptionMenu(master, optionvalue, 1, 2, 3, 4)
    self.optionmenu.pack()

I am trying to implement an OptionMenu (a Tkinter widget) such that when its selected value is changed, my callback function does something meaningful---more specifically, it will change a global variable value, defined somewhere else in the program. As it is implemented above, it simply prints output (albeit successfully).

I cannot figure out how to pass parameters to my callback function. I do not want this particular callback function to return anything; however, I am curious as to how I would make my callback function return something, and how I would implement the rest of my program so that it could utilize those returned results, whatever those might be. Am I trying to implement a Python callback function in a way in which it was not intended to be implemented? If not, how do I make this one work?

nairware
  • 3,090
  • 9
  • 37
  • 58

3 Answers3

4

It's a little bit unclear what you mean by "pass parameters to my callback function." You're already doing that! For example:

from Tkinter import *

def callbackfunc(*args, **kwargs):
    print args, kwargs
    print "Hello World!"

class App(object):
    def __init__(self, master):
        frame = Frame(master)
        frame.pack()

        optionvalue = IntVar(master)
        optionvalue.set(2)
        optionvalue.trace("w", callbackfunc)
        self.optionmenu = OptionMenu(master, optionvalue, 1, 2, 3, 4)
        self.optionmenu.pack()

root = Tk()
app = App(root)
root.mainloop()

When run...

$ python foo.py 
('PY_VAR0', '', 'w') {}
Hello World!

So you see, when Tkinter calls your callback, it passes parameters to it. If you wanted to do something other than print them, you could store them in some state, by passing a method instead of a function.

from Tkinter import *

class App(object):
    def __init__(self, master):
        frame = Frame(master)
        frame.pack()

        optionvalue = IntVar(master)
        optionvalue.set(2)
        optionvalue.trace("w", self.callbackfunc)
        self.optionmenu = OptionMenu(master, optionvalue, 1, 2, 3, 4)
        self.optionmenu.pack()
        self.state = []

    def callbackfunc(self, *args):
        self.state.append(args)
        print self.state


root = Tk()
app = App(root)
root.mainloop()

When run...

$ python foo.py 
[('PY_VAR0', '', 'w')]
[('PY_VAR0', '', 'w'), ('PY_VAR0', '', 'w')]
[('PY_VAR0', '', 'w'), ('PY_VAR0', '', 'w'), ('PY_VAR0', '', 'w')]

Also, perhaps you want to access the value of optionvalue. You could save a reference to it then:

from Tkinter import *

class App(object):
    def __init__(self, master):
        frame = Frame(master)
        frame.pack()

        self.optionvalue = IntVar(master)
        self.optionvalue.set(2)
        self.optionvalue.trace("w", self.callbackfunc)
        self.optionmenu = OptionMenu(master, self.optionvalue, 1, 2, 3, 4)
        self.optionmenu.pack()
        self.state = []

    def callbackfunc(self, *args):
        self.state.append(args)
        print self.state
        print self.optionvalue.get()


root = Tk()
app = App(root)
root.mainloop()

When run...

$ python foo.py 
[('PY_VAR0', '', 'w')]
1
[('PY_VAR0', '', 'w'), ('PY_VAR0', '', 'w')]
2
[('PY_VAR0', '', 'w'), ('PY_VAR0', '', 'w'), ('PY_VAR0', '', 'w')]
3

You could also use root.getvar(name) with name = 'PY_VAR0' (the first arg passed to the callback), as noob oddy suggests.

Community
  • 1
  • 1
senderle
  • 145,869
  • 36
  • 209
  • 233
  • You are right, I did not make perfect sense. I meant to say "additional" parameters...which is what you passed with "**kwargs" in your first example. Although, I was also confused about how to pass parameters within the trace (when you write "w", callbackfunc). I cannot pass anything to callbackfunc that way right? – nairware Mar 26 '12 at 03:16
  • @nairware, you can't pass anything to callbackfunc that way, because you aren't _calling_ the function. It's a callback, which means that only _Tkinter_ ever calls it, and only passes to it the things that you see above. `**kwargs` was not an "additional parameter" -- it's just the keyword version of `*args` (i.e. it accepts [keyword rather than positional arguments](http://stackoverflow.com/questions/1419046/python-normal-arguments-vs-keyword-arguments). Furthermore, using `*args` accepts all "additional parameters" that get thrown at it (by Tkinter). – senderle Mar 26 '12 at 13:03
  • @nairware, what you _can_ do, and perhaps what you mean to do, is create a function that _returns_ a function, pass a parameter to that function, and pass the returned function as the callback. If that's something you want to do, let me know. – senderle Mar 26 '12 at 13:04
  • Alright, I believe I now know enough now to solve my specific problem. Thanks for the help. – nairware Mar 26 '12 at 13:31
2

If you want to send additional argument -- or only the arguments of your choosing -- you can use a lambda. Some people find lambda confusing, but it can be a powerful tool.

define your callback however you want:

def callbackfunc(message=None):
  print message

use lambda to accept the arguments passed in by Tkinter and keyword arguments for whatever additional arguments you want to send, then call your callback with whichever of those arguments you want. For example:

    optionvalue.trace("w", lambda name, index, op, 
                      message="hello, world": callbackfunc(message))

You can also use functools.partial to do the same thing in an arguably easier-to-read syntax.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
1
from Tkinter import *

def callbackfunc(name, index, mode):
    print root.getvar(name)

class App:

    def __init__(self, master):
        frame = Frame(master)
        frame.pack()

        optionvalue = IntVar(master)
        optionvalue.set(2)
        optionvalue.trace("w", callbackfunc)
        self.optionmenu = OptionMenu(master, optionvalue, 1, 2, 3, 4)
        self.optionmenu.pack()

root = Tk()
app = App(root)
root.mainloop()
noob oddy
  • 1,314
  • 8
  • 11