#Question: Why does my Tkinter Button command execute early and only once?

I want to create a GUI, where I can click a button, and it will print a message on the command line.

#compatibility note: 
#Python 2.7 users should import Tkinter; 3.X users should import tkinter.
from tkinter import Tk, Button
def excited(message):
    print(message + "!")

root = Tk()
button = Button(text="Click Me!", command=excited("Hello, World"))
button.pack()
root.mainloop()

When I run this, it prints “Hello, World!” once during startup, and clicking the button has no effect. Why is this?


#Answer

##What’s Going On

button = Button(text="Click Me!", command=excited("Hello, World"))

This code is equivalent to:

result = excited("Hello, World")
button = Button(text="Click Me!", command=result)

Since you’re calling excited before you even create the button, “Hello, World!” is printed at the start of the program.

excited has no explicit return value, so when you call it, it returns None. Your Button initialization is effectively the same as:

button = Button(text="Click Me!", command=None)

Since command is None, nothing happens when you click the button.

##How to Fix it

One possible solution is to only bind functions that take no arguments:

from tkinter import Tk, Button
def excited():
    print("Hello, World!")

root = Tk()
#be sure to NOT put parentheses after `excited` on this next line
button = Button(text="Click Me!", command=excited) 
button.pack()
root.mainloop()

But this may be impossible. What if you have to pass arguments? In that case, you can use a lambda expression to construct a callable with no arguments.

from tkinter import Tk, Button
def excited(message):
    print(message + "!")

root = Tk()
button = Button(text="Click Me!", command=lambda: excited("Hello, World"))
button.pack()
root.mainloop()

Simply put, because excited("Hello, World") is in a lambda, it won’t execute right away, instead waiting until the button is clicked.