6

How can I get a returned object from a function that is executed as a Tkinter callback?

import Tkinter as Tk
from functools import partial

def square(x):
    return x*x

root = Tk.Tk()
var = Tk.IntVar(root, value=0) #the variable the gets passed to the class call
menu = Tk.OptionMenu(root, var, *[0,1,2,3,4,5]) #a drop-down list to choose a value for the variable
menu.pack()
button = Tk.Button(root, text='click', command = partial(square,var.get())) #a button that calls the class
button.pack()
root.mainloop()

Obviously this is a simplified example. In reality the function called by the button will return objects, which I wish to append to a list of objects that will be held in the main Python namespace for further operations.

Anyway, here the user is able to choose an argument for the function using a GUI, and press a button that will execute the function. The return value of the function, however, seems doomed to be lost to the aether, since the callback won't accept returns. Can this be overcome without the use of an ugly global in the definition of square(x)?

2
  • 1
    Actually, I think the call to var.get() inside the partial might not actually work, since it'll be evaluated when the button is created, not when it's clicked. Don't let this cloud the issue, though - my question is really about the return from square(x) into the button. Commented Aug 10, 2012 at 14:36
  • 1
    You can get around that by replacing the call to partial with a lambda function: command = lambda x=var.get(): square(x). You can also set up a var.trace(). Commented Aug 10, 2012 at 14:57

3 Answers 3

11

The notion of "returning" values from callbacks doesn't make sense in the context of an event driven program. Callbacks are called as the result of an event, so there's nowhere to return a value to.

As a general rule of thumb, your callbacks should always call a function, rather than using functools.partial or lambda. Those two are fine when needed, but if you're using an object-oriented style of coding they are often unnecessary, and lead to code that is more difficult to maintain than it needs to be.

For example:

def compute():
    value = var.get()
    result = square(value)
    list_of_results.append(result)

button = Tk.Button(root, text='click', command = compute)
...

This becomes much easier, and you can avoid global variables, if you create your application as a class:

class App(...):
    ...
    def compute():
        ...
        result = self.square(self.var.get())
        self.results.append(result)
Sign up to request clarification or add additional context in comments.

2 Comments

The list_of_results.append() line in your first example makes me a little squeamish. It feels weird for a function to modify variables outside of its scope, whether or not you explicitly call it a global variable. Implementing the whole program as a class is a nice idea though.
@poorsod: There is no choice but for it to alter variables outside its local scope. That's how GUIs work. Like I said, callbacks can't return values to their caller because they have no caller (other than the event loop). However, when you think of the callback happening "in the scope of the application object", it makes more sense. The callback is merely changing an attribute of its own object.
9

Sorry for being 6 years late, but recently I figured out a good way to do this without making your code messy and hard to maintain. This is pretty much what DaveTheScientist has said, but I just want to expand on it a little. Usually, in Tkinter, if you want to have a button call a function, you do the following:

exampleButton = Button(root, text = 'Example', command = someFunc)

This will simply call someFunc whenever the button is pressed. If this function, however, takes arguments, you need to use lambdas and do something like this:

exampleButton = Button(root, text = 'Example', command = lambda: someFunc(arg1, arg2))

The above line of code will run someFunc and use the variables arg1 and arg2 as arguments for that function. Now, what you could do in a program where, a lot of the times, you would need the functions run by buttons to return values, is create a new function which is called by every button.

This function takes the function you want your button to run as a first argument, and that function's arguments afterwards.

def buttonpress(function, *args):
    value = function(*args)

Then when you create the button, you do:

exampleButton = Button(root, text = 'Example', command = lambda: buttonpress( someFunc, arg1, arg2 ))

This will run the given function (in this case, someFunc) and store the returned value in the value variable. It also has the advantage that you can use as many arguments as you want for the function your button runs.

Comments

0

Just create an actual function that is called by your button, instead of putting it all inline like that.

button=Tk.Button(parent, text='click', command=someFxn)

def someFxn(): your code

Then in your function just call the var.get(), do your calculation, and then do something with the value.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.