How to pass arguments to a Button command in Tkinter?
Solution 1
I personally prefer to use lambdas
in such a scenario, because imo it's clearer and simpler and also doesn't force you to write lots of wrapper methods if you don't have control over the called method, but that's certainly a matter of taste.
That's how you'd do it with a lambda (note there's also some implementation of currying in the functional module, so you can use that too):
button = Tk.Button(master=frame, text='press', command= lambda: action(someNumber))
Solution 2
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)
Solution 3
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()
Solution 4
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.
Solution 5
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))
Related videos on Youtube
Jack
Updated on July 22, 2022Comments
-
Jack over 1 year
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 methodaction
?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.
-
noob oddy over 12 yearsframe = Tk.Frame(master=win).grid(row=1, column=1) # Q. what is the value of frame now ?
-
-
agf over 12 yearsYou're still writing wrapper methods, you're just doing it inline.
-
Voo over 12 yearsSure but I hoped it was obvious from context how that was meant. Also the advantages still stand: Shorter and you can more easily use it on methods you don't have direct control over, or still want to be able to simply call directly. I find the inner method variant only useful for more complex scenarios (eg for decorators; although there a class is imo clearer too)
-
eyquem about 11 years@Voo What is the non-wrapper solution to which agf seems to allude to, please ?
-
Russell Smith about 11 yearsThis does not solve the problem. What if you are creating three buttons that all call the same function but need to pass different arguments?
-
Daniel almost 10 yearsthere is no non-wrapper solution.
-
Scrontch almost 10 yearsThis 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. -
Voo almost 10 years@Scrontch Well yeah obviously that's how variable scoping works in python and pretty much every other language (so it works and if you think about it, it's the only logical way to do things). Several solutions, partial certainly works although old school python people will probably stick to the default parameter assignment for the lambda.
-
Tommy over 9 yearsThis 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? -
WeRelic over 9 yearsOT: Went out of my way to upvote this, thank you. @Tommy, Just taking a guess, but I think it is because Python handles functions in two stages. callables and actual calls(still new so I don't know the terms). By passing args to a callable, you take the arg from being a reference to a type object to being a call to that object with the specified parameters.
-
gboffi over 9 years@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 infs = [lambda x=x: x*2 for x in range(5)] ; print [f() for f in fs]
-
MarrekNožka over 9 yearsYou can create a function Which returns a function.
-
Klamer Schutte about 9 yearsOr even shorter:
button = Tk.Button(master=frame, text='press', command=partial(action, arg))
-
Klamer Schutte about 9 years@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.
-
m3nda over 8 yearsMy personal case is that
command=something()
does action even if no click is made on button. So strange behavior. Using lambda function constructor is so much better, also allows you to extend the function and does what i expect, run on click :P -
Voo over 8 years@erm3nda Leave the parens off - you don't want to call that function.
-
m3nda over 8 years@Voo: Ask Python devs to remove it from the Python functions and you will know why is in there. Lambda is nothing new, double iteration over a function, literally function constructor. Use it with care ;)
-
Voo over 8 years@erm3nda I have no idea what you're trying to tell me. Your parameter should be
command=something
without the parens otherwise you assign the result of your function to the parameter which you almost certainly don't want. -
m3nda over 8 yearsI got the same problem than the asker. Even if in theory it should work, doesn't. Every function defined into a button does fire at load, and not when you press button. Did u try in your machine that piece of code?
-
Tadhg McDonald-Jensen about 8 yearsI know this isn't active any more but I linked to here from stackoverflow.com/questions/35616411/…, this works the exact same way as using lambda expressions, you can define a function for each button in the same way as making a lambda expression for each button.
-
Tadhg McDonald-Jensen about 8 yearsputting the first code example in a loop that keeps changing
myX
andmyY
works perfectly thank you very much. -
z33k about 6 yearsMore on the issue pointed by @Scrontch
-
elig over 5 yearsThis trick actually works, but sadly not with .bind() ; @erm3nda It shouldn't work in "theory". That's how Python is supposed to work. You put a function name with '()' - it gets called immediately !
-
Voo over 5 years@elig Not if you put a lambda: in front of it (that's how you designate an anonymous function that takes no arguments and returns the result of the function call), which is why the shown code works just fine.
-
mashedpotatoes over 4 yearsSorry for necro'ing, but how would you do this in the case of two or more arguments?
-
Dologan over 4 years
action_with_args = partial(action, arg1, arg2... argN)
-
Raymond Reddington about 4 yearsI'd take this approach because lambda didn't work for me.
-
Brom about 4 yearsAs your code has put
var1
andvar2
in main module, you can skipfunction1
at all and putprint
statement infunction2
. Do you refer to other situations which I don't get it? -
Metal3d about 3 yearsThis should be the valid solution
-
greggT about 2 yearsI 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.