275

Suppose I have the following Button made with Tkinter in Python:

import Tkinter as Tk
win = Tk.Toplevel()
frame = Tk.Frame(master=win).grid(row=1, column=1)
button = Tk.Button(master=frame, text='press', command=action)

The method action is called when I press the button, but what if I wanted to pass some arguments to the method action?

I have tried with the following code:

button = Tk.Button(master=frame, text='press', command=action(someNumber))

This just invokes the method immediately, and pressing the button does nothing.


See Python Argument Binders for standard techniques (not Tkinter-specific) for solving the problem. Working with callbacks in Tkinter (or other GUI frameworks) has some special considerations because the return value from the callback is useless.

If you try to create multiple Buttons in a loop, passing each one different arguments based on the loop counter, you may run into problems due to what is called late binding. Please see tkinter creating buttons in for loop passing command arguments for details.

1
  • 5
    frame = Tk.Frame(master=win).grid(row=1, column=1) # Q. what is the value of frame now ?
    – noob oddy
    Commented Aug 3, 2011 at 10:05

14 Answers 14

412

This can be done using a lambda, like so:

button = Tk.Button(master=frame, text='press', command= lambda: action(someNumber))

This is a simple way to bind the argument without an explicit wrapper method or modifying the original action.

18
  • 17
    You're still writing wrapper methods, you're just doing it inline.
    – agf
    Commented Aug 3, 2011 at 20:29
  • 96
    This doesn't work if someNumber is in fact a variable that changes values inside a loop that creates many buttons. Then each button will call action() with the last value that has been assigned to someNumber and not the value it had when the button was created. The solution using partial works in this case.
    – Scrontch
    Commented Jul 18, 2014 at 13:46
  • 1
    This worked great for me. However, can you also explain why the OPs statement of "This just invokes the method immediately, and pressing the button does nothing" happens?
    – Tommy
    Commented Jul 20, 2014 at 16:56
  • 23
    @Scrontch I wonder how many novice Tkinter users never felt in the trap you mentioned! At any rate one can capture the current value using the idiom callback=lambda x=x: f(x) as in fs = [lambda x=x: x*2 for x in range(5)] ; print [f() for f in fs]
    – gboffi
    Commented Nov 10, 2014 at 0:34
  • 1
    @Voo what do you mean above with "although old school python people will probably stick to the default parameter assignment for the lambda"? I did not get lambda to work and thus now use partial. Commented Feb 18, 2015 at 22:14
157

This can also be done by using partial from the standard library functools, like this:

from functools import partial
#(...)
action_with_arg = partial(action, arg)
button = Tk.Button(master=frame, text='press', command=action_with_arg)
8
  • 47
    Or even shorter: button = Tk.Button(master=frame, text='press', command=partial(action, arg)) Commented Feb 18, 2015 at 22:11
  • 2
    Sorry for necro'ing, but how would you do this in the case of two or more arguments? Commented Aug 5, 2019 at 18:41
  • 12
    action_with_args = partial(action, arg1, arg2... argN)
    – Dologan
    Commented Aug 6, 2019 at 8:09
  • 3
    I'd take this approach because lambda didn't work for me. Commented Feb 5, 2020 at 8:03
  • 1
    Does this work if arg is a string whose value changes during a loop? I want to create many buttons, each of them has to call the same function but with a different value of the argument when pressed. Is this possible? Commented Jul 14, 2022 at 15:42
33

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(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

2
  • 1
    I think you did not answer the OPs question of multiple buttons in a loop, in which case your 'arg' and 'kw' parameters would all have the value of the last iteration of the loop. you could rewrite your answer to use 'partial', which solves this issue.
    – greggT
    Commented Mar 17, 2022 at 14:37
  • @greggT As far as I can tell, OP didn't ask about multiple buttons in a loop. However, please see tkinter creating buttons in for loop passing command arguments. Commented Aug 18, 2022 at 3:50
13

Python's ability to provide default values for function arguments gives us a way out.

def fce(x=myX, y=myY):
    myFunction(x,y)
button = Tk.Button(mainWin, text='press', command=fce)

See: https://tkdocs.com/shipman/extra-args.html

For more buttons you can create a function which returns a function:

def fce(myX, myY):
    def wrapper(x=myX, y=myY):
        pass
        pass
        pass
        return x+y
    return wrapper

button1 = Tk.Button(mainWin, text='press 1', command=fce(1,2))
button2 = Tk.Button(mainWin, text='press 2', command=fce(3,4))
button3 = Tk.Button(mainWin, text='press 3', command=fce(9,8))
0
11

Just to make the answer of Nae a little bit more elaborate, here is a full blown example which includes the possibility to pass a variable to the callback which contains different values for each button:

import tkinter as tk
    
def callback(text):
    print(text)

top = tk.Tk()
Texts=["text1", "text2", "text3"]
Buttons=[]

for i, z in enumerate(Texts):
    Buttons.append(tk.Button(top, text=z, command= lambda ztemp=z : callback(ztemp)))
    Buttons[i].pack(side=tk.LEFT, padx=5)

top.mainloop()

By defining a temporary variable ztemp, the value of that variable gets fixed at the moment when the button is defined.

1
6

Building on Matt Thompsons answer : a class can be made callable so it can be used instead of a function:

import tkinter as tk

class Callback:
    def __init__(self, func, *args, **kwargs):
        self.func = func
        self.args = args
        self.kwargs = kwargs
    def __call__(self):
        self.func(*self.args, **self.kwargs)

def default_callback(t):
    print("Button '{}' pressed.".format(t))

root = tk.Tk()

buttons = ["A", "B", "C"]

for i, b in enumerate(buttons):
    tk.Button(root, text=b, command=Callback(default_callback, b)).grid(row=i, column=0)

tk.mainloop()
5

Use lambda

import tkinter as tk

root = tk.Tk()
def go(text):
    print(text)

b = tk.Button(root, text="Click", command=lambda: go("hello"))
b.pack()
root.mainloop()

output:

hello
3

One simple way would be to configure button with lambda like the following syntax:

button['command'] = lambda arg1 = local_var1, arg2 = local_var2 : function(arg1, arg2)
3

Lambdas are all well and good, but you can also try this (which works in a for loop btw):

root = Tk()

dct = {"1": [*args], "2": [*args]}
def keypress(event):
    *args = dct[event.char]
    for arg in args:
        pass
for i in range(10):
    root.bind(str(i), keypress)

This works because when the binding is set, a key press passes the event as an argument. You can then call attributes off the event like event.char to get "1" or "UP" ect. If you need an argument or multiple arguments other than the event attributes. just create a dictionary to store them.

1
  • This is worth highlighting as a Tkinter-specific approach to the problem. Commented Apr 3, 2023 at 20:17
2

The reason it invokes the method immediately and pressing the button does nothing is that action(somenumber) is evaluated and its return value is attributed as the command for the button. So if action prints something to tell you it has run and returns None, you just run action to evaluate its return value and given None as the command for the button.

To have buttons to call functions with different arguments you can use global variables, although I can't recommend it:

import Tkinter as Tk

frame = Tk.Frame(width=5, height=2, bd=1, relief=Tk.SUNKEN)
frame.grid(row=2,column=2)
frame.pack(fill=Tk.X, padx=5, pady=5)
def action():
    global output
    global variable
    output.insert(Tk.END,variable.get())
button = Tk.Button(master=frame, text='press', command=action)
button.pack()
variable = Tk.Entry(master=frame)
variable.pack()
output = Tk.Text(master=frame)
output.pack()

if __name__ == '__main__':
    Tk.mainloop()

What I would do is make a class whose objects would contain every variable required and methods to change those as needed:

import Tkinter as Tk
class Window:
    def __init__(self):
        self.frame = Tk.Frame(width=5, height=2, bd=1, relief=Tk.SUNKEN)
        self.frame.grid(row=2,column=2)
        self.frame.pack(fill=Tk.X, padx=5, pady=5)

        self.button = Tk.Button(master=self.frame, text='press', command=self.action)
        self.button.pack()

        self.variable = Tk.Entry(master=self.frame)
        self.variable.pack()

        self.output = Tk.Text(master=self.frame)
        self.output.pack()

    def action(self):
        self.output.insert(Tk.END,self.variable.get())

if __name__ == '__main__':
    window = Window()
    Tk.mainloop()
2

I am extremely late, but here is a very simple way of accomplishing it.

import tkinter as tk
def function1(param1, param2):
    print(str(param1) + str(param2))

var1 = "Hello "
var2 = "World!"
def function2():
    function1(var1, var2)

root = tk.Tk()

myButton = tk.Button(root, text="Button", command=function2)
root.mainloop()

You simply wrap the function you want to use in another function and call the second function on the button press.

1
  • As your code has put var1 and var2 in main module, you can skip function1 at all and put print statement in function2. Do you refer to other situations which I don't get it?
    – Brom
    Commented Feb 27, 2020 at 7:33
2

Another way is by defining a class and using instance attributes to remember arguments for the callback. To implement the callback, we can simply use a method of the class which looks up the attribute values from self - it doesn't need to have separate parameters at all.

Create the class like so:

class Function_Wrapper():
    def __init__(self, x, y, z):
        self.x, self.y, self.z = x, y, z
    def func(self):
        return self.x + self.y + self.z # execute function

Then make buttons like:

instance1 = Function_Wrapper(x, y, z)
button1  = Button(master, text = "press", command = instance1.func)

This approach also allows changing the argument bindings later by simply modifying the class instance:

instance1.x = 3
# when `button1` is clicked later and `instance1.func` is called,
# `self.x` will use the new value.
0

Use a lambda to pass the entry data to the command function if you have more actions to carry out, like this (I've tried to make it generic, so just adapt):

event1 = Entry(master)
button1 = Button(master, text="OK", command=lambda: test_event(event1.get()))

def test_event(event_text):
    if not event_text:
        print("Nothing entered")
    else:
        print(str(event_text))
        #  do stuff

This will pass the information in the event to the button function. There may be more Pythonesque ways of writing this, but it works for me.

0

First: if the code to create a button is put in a loop, the Button instance will be re-created each time through the loop, which may or may not be desirable.

The reason it always gets the last index is that the callback runs when the button is clicked, not when the program starts. Try storing the value of the variable when the button is created, then have the lambda use the stored value.

For example:

for entry in stuff_that_is_happening:
    value_store[entry] = stuff_that_is_happening

Then, later:

Button(..., command= lambda: value_store[1])

Not the answer you're looking for? Browse other questions tagged or ask your own question.