Why is the command bound to a Button or event executed when declared?

PythonTkinter

Python Problem Overview


My code is:

from Tkinter import *

admin = Tk()
def button(an):
    print an
    print 'het'

b = Button(admin, text='as', command=button('hey'))
b.pack()
mainloop()

The button doesn't work, it prints 'hey' and 'het' once without my command, and then, when I press the button nothing happens.

Python Solutions


Solution 1 - Python

Consider this code:

b = Button(admin, text='as', command=button('hey'))

It does exactly the same as this:

result = button('hey')
b = button(admin, text='as', command=result)

Likewise, if you create a binding like this:

listbox.bind("<<ListboxSelect>>", some_function())

... it's the same as this:

result = some_function()
listbox.bind("<<ListboxSelect>>", result)

The command option takes a reference to a function, which is a fancy way of saying you need to pass it the name of the function. To pass a reference you must use the name only, without using parenthesis or arguments. For example:

b = Button(... command = button)

If you want to pass a parameter such as "hey" you must use a little extra code:

  • You can create an intermediate function that can be called without your argument and which then calls your button function,
  • You can use lambda to create what is referred to as an anonymous function. In every way it's a function except it doesn't have a name. When you call the lambda command it returns a reference to the created function, which means it can be used for the value of the command option to the button.
  • You can use functools.partial

For me, lambda is the simplest since it doesn't require any additional imports like functools.partial does, though some people think that functools.partial is easier to understand.

To create a lambda function that calls your button function with an argument you would do something like this:

lambda: button('hey')

You end up with a function that is functionally equivalent to:

def some_name():
    return button('hey')

As I said earlier, lambda returns a reference to this nameless function. Since a reference is what the command option expects you can use lambda directly in the creation of the button:

b = Button(... command = lambda: button('hey'))

There's a question on this site that has a lot of interesting comments about lambda, in general. See the question Why Python lambdas are useful?. That same discussion has an answer that shows how to use lambdas in a loop when you need to pass in a variable to the callback.

Finally, see the zone.effbot.org article titled Tkinter Callbacks for a nice tutorial. The coverage of lambda is pretty lean, but the information there might still be useful.

Solution 2 - Python

You need to create a function without parameters that you can use as the command:

b = Button(admin, text='as', command=lambda: button('hey'))

See the "Passing Argument to Callbacks" section of this document.

Solution 3 - Python

Example GUI:

Let's say I have the GUI:

import tkinter as tk

root = tk.Tk()

btn = tk.Button(root, text="Press")
btn.pack()

root.mainloop()
What Happens When a Button Is Pressed

See that when btn is pressed it calls its own function which is very similar to button_press_handle in the following example:

def button_press_handle(callback=None):
    if callback:
        callback() # Where exactly the method assigned to btn['command'] is being callled

with:

button_press_handle(btn['command'])

You can simply think that command option should be set as, the reference to the method we want to be called, similar to callback in button_press_handle.


Calling a Method (a Callback) When the Button is Pressed

Without arguments

So if I wanted to print something when the button is pressed I would need to set:

btn['command'] = print # default to print is new line

Pay close attention to the lack of () with the print method which is omitted in the meaning that: "This is the method's name which I want you to call when pressed but don't call it just this very instant." However, I didn't pass any arguments for the print so it printed whatever it prints when called without arguments.

With Argument(s)

Now If I wanted to also pass arguments to the method I want to be called when the button is pressed I could make use of the anonymous functions, which can be created with lambda statement, in this case for print built-in method, like the following:

btn['command'] = lambda arg1="Hello", arg2=" ", arg3="World!" : print(arg1 + arg2 + arg3)

Calling Multiple Methods when the Button Is Pressed

Without Arguments

You can also achieve that using lambda statement but it is considered bad practice and thus I won't include it here. The good practice is to define a separate method, multiple_methods, that calls the methods wanted and then set it as the callback to the button press:

def multiple_methods():
    print("Vicariously") # the first inner callback
    print("I") # another inner callback

With Argument(s)

In order to pass argument(s) to method that calls other methods, again make use of lambda statement, but first:

def multiple_methods(*args, **kwargs):
    print(args[0]) # the first inner callback
    print(kwargs['opt1']) # another inner callback

and then set:

btn['command'] = lambda arg="live", kw="as the" : a_new_method(arg, opt1=kw)

Returning Object(s) From the Callback

Also further note that callback can't really return because it's only called inside button_press_handle with callback() as opposed to return callback(). It does return but not anywhere outside that function. Thus you should rather modify object(s) that are accessible in the current scope.


Complete Example with global Object Modification(s)

Below example will call a method that changes btn's text each time the button is pressed:

import tkinter as tk

i = 0
def text_mod():
    global i, btn           # btn can be omitted but not sure if should be
    txt = ("Vicariously", "I", "live", "as", "the", "whole", "world", "dies")
    btn['text'] = txt[i]    # the global object that is modified
    i = (i + 1) % len(txt)  # another global object that gets modified

root = tk.Tk()

btn = tk.Button(root, text="My Button")
btn['command'] = text_mod

btn.pack(fill='both', expand=True)

root.mainloop()

Mirror

Solution 4 - Python

The engine evaluates the result of the function when it is assigning the value at the line "... command = ..."

The "command" expects a function to be returned, that's why using a lambda can do the job because it is creating an anomymous function that is returned to the "command" during evaluation. You can also code your own function, it will do the job also.

this is an example with lambda and without lambda:

#!/usr/bin/python
# coding=utf-8

from Tkinter import *
# Creation de la fenêtre principale (main window)
Mafenetre = Tk()
res1 = StringVar()
res2 = StringVar()

def isValidInput(obj):
    if hasattr(obj, 'get') and callable(getattr(obj, 'get')):
        return TRUE
    return FALSE


# stupid action 2 (return 12 on purpose to show potential mistake)
def action1(*arguments):
    print "action1 running"
    for arg in arguments:
        if isValidInput(arg):
            print "input value: ", arg.get()
            res1.set(arg.get())
        else:
            print "other value:", arg
    print "\n"
    return 12


# stupid action 2
def action2(*arguments):
    print "action2 running"
    a = arguments[0]
    b = arguments[1]
    if isValidInput(a) and isValidInput(b):
        c = a.get() + b.get()
        res2.set(c)
        print c
    print "\n"


# a stupid workflow manager ordered by name
def start_tasks(*arguments, **keywords):
    keys = sorted(keywords.keys())
    for kw in keys:
        print kw, "plugged "
        keywords[kw](*arguments)


# valid callback wrapper with lambda
def action1_callback(my_input):
    return lambda args=[my_input]: action1(*args)


# valid callback wrapper without lambda
def action1_callback_nolambda(*args, **kw):
    def anon():
        action1(*args)
    return anon


# first input string
input1 = StringVar()
input1.set("delete me...")
f1 = Entry(Mafenetre, textvariable=input1, bg='bisque', fg='maroon')
f1.focus_set()
f1.pack(fill="both", expand="yes", padx="5", pady=5)

# failed callback because the action1 function is evaluated, it will return 12. 
# in this case the button won't work at all, because the assignement expect a function 
# in order to have the button command to execute something
ba1 = Button(Mafenetre)
ba1['text'] = "show input 1 (ko)"
ba1['command'] = action1(input1)
ba1.pack(fill="both", expand="yes", padx="5", pady=5)

# working button using a wrapper
ba3 = Button(Mafenetre)
ba3['text'] = "show input 1 (ok)"
# without a lambda it is also working if the assignment is a function
#ba1['command'] = action1_callback_nolambda(input1)
ba3['command'] = action1_callback(input1)
ba3.pack(fill="both", expand="yes", padx="5", pady=5)

# display result label
Label1 = Label(Mafenetre, text="Action 1 result:")
Label1.pack(fill="both", expand="yes", padx="5", pady=5)
# display result value
resl1 = Label(Mafenetre, textvariable=res1)
resl1.pack(fill="both", expand="yes", padx="5", pady=5)


# second input string
input2 = StringVar()
f2 = Entry(Mafenetre, textvariable=input2, bg='bisque', fg='maroon')
f2.focus_set()
f2.pack(fill="both", expand="yes", padx="5", pady=5)

# third test without wrapper, but making sure that several arguments are well handled by a lambda function
ba2 = Button(Mafenetre)
ba2['text'] = "execute action 2"
ba2['command'] = lambda args=[input1, input2], action=action2: start_tasks(*args, do=action)
ba2.pack(fill="both", expand="yes", padx="5", pady=5)

# display result label
Label2 = Label(Mafenetre, text="Action 2 result:")
Label2.pack(fill="both", expand="yes", padx="5", pady=5)
# display result value
resl2 = Label(Mafenetre, textvariable=res2)
resl2.pack(fill="both", expand="yes", padx="5", pady=5)

Mafenetre.mainloop()

Solution 5 - Python

I think the best way to solve this problem is to use a lambda function.

from tkinter import *
admin= Tk()
def button(an):
    print(an)
    print("het")
b = Button(admin, text="as", command=lambda: button("hey"))
b.pack()
mainloop()

If you don't want to use the command keyword, you can use the .bind() method instead:

from tkinter import *
admin= Tk()
def button(an):
    print(an)
    print("het")
b = Button(admin, text="as")
b.pack()
b.bind("<Button-1>", lambda bb: button("hey"))
mainloop()

Using a mother function (no parameter) which owns the child function (at least 1 parameter) you want to call is stupid.

Just to share with you, this is one of my program:

import tkinter
window = tkinter.Tk()

def plus_them(field_1, field_2, field_3):
    field_3.delete(0, 'end')
    num1 = 0
    num2 = 0
    try:
        num1 = int(field_1.get())
        num2 = int(field_2.get())
    except:
        print("Exception occurs")
    else:
        print("Continue")
    result = num1 + num2
    field_3.insert(tkinter.END, str(result))
    return result
def minus_them(field_1, field_2, field_3):
    field_3.delete(0, 'end')
    num1 = 0
    num2 = 0
    try:
        num1 = int(field_1.get())
        num2 = int(field_2.get())
    except:
        print("Exception occurs")
    else:
        print("Continue")
    result = num1 - num2
    field_3.insert(tkinter.END, str(result))
    return result

#Input Panel:
label_1 = tkinter.Label(window, text="First Number:")
label_1.grid(row=0, column=0)
label_2 = tkinter.Label(window, text="Second Number:")
label_2.grid(row=1, column=0)
entry_1 = tkinter.Entry(window)
entry_1.grid(row=0, column=1)
entry_2 = tkinter.Entry(window)
entry_2.grid(row=1, column=1)

#Button Panel:
button_1 = tkinter.Button(window, text="Plus")
button_1.grid(row=2, column=0)
button_2 = tkinter.Button(window, text="Minus")
button_2.grid(row=2, column=1)

#Answer Panel:
label_3 = tkinter.Label(window, text="The Answer:")
label_3.grid(row=3, column=0)
entry_3 = tkinter.Entry(window)
entry_3.grid(row=3, column=1)

#Event Handling:
button_1.bind("<Button-1>", lambda p: plus_them(entry_1, entry_2, entry_3))
button_2.bind("<Button-1>", lambda m: minus_them(entry_1, entry_2, entry_3))

#Window Stuff:
window.title("Plus and Minus Calculator")
window.mainloop()

That's it.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionsalkView Question on Stackoverflow
Solution 1 - PythonBryan OakleyView Answer on Stackoverflow
Solution 2 - PythonLukáš LalinskýView Answer on Stackoverflow
Solution 3 - PythonNaeView Answer on Stackoverflow
Solution 4 - PythonC.VergnaudView Answer on Stackoverflow
Solution 5 - PythonDeer LawsonView Answer on Stackoverflow