How do I pass arguments to a callback for a StringVar? - python

I have set up an Entry widget using tkinter and am using a StringVar in order to trace any changes to it. However, when I try to use the callback function, I find that I am unable to pass any arguments to it.
Here is what I have tried:
hpstring = StringVar()
hpstring.trace("w", hptrace)
hpstring.set("0")
hpbox = tk.Entry(frame, textvariable=hpstring)
hpbox.pack()
def hptrace(*args):
print(hpstring)
This fails because hpstring is not defined.
hpstring = StringVar()
hpstring.trace("w", hptrace(hpstring))
hpstring.set("0")
hpbox = tk.Entry(frame, textvariable=hpstring)
hpbox.pack()
def hptrace(*args):
print(hpstring)
This also somehow fails.
hpstring = StringVar()
hpstring.trace("w", lambda hpstring: hptrace(hpstring))
hpstring.set("0")
hpbox = tk.Entry(frame, textvariable=hpstring)
hpbox.pack()
def hptrace(*args):
print(hpstring)
This time, it fails because "() takes 1 positional argument but 3 were given".
Is there any way to have my callback function (hptrace) take hpstring as an argument?

Is there any way to have my callback function (hptrace) take hpstring as an argument?
You can use lambda or functools.partial. The important thing to remember is that the function called by the trace is called with three arguments, so whatever command you give to trace needs to accept those three arguments.
Using lambda, your code should look something like this if you want to send only hpstring to hptrace:
hpstring.trace("w", lambda var_name, var_index, operation: hptrace(hpstring))
Note: you need to define hptrace before you attempt to use it, which the code in your question fails to do.

Related

Understanding Python Lambda behavior with Tkinter Button [duplicate]

This question already has answers here:
Why is my Button's command executed immediately when I create the Button, and not when I click it? [duplicate]
(5 answers)
Closed 1 year ago.
I would like to understand how a button is working using lambda.
I have the following Python code:
from tkinter import *
def comando_click(mensagem):
print(mensagem)
menu_inicial = Tk()
menu_inicial.geometry("500x250+200+200")
botao = Button(menu_inicial, text = "Executar", command=comando_click("Nova_Mensagem"))
botao.pack()
menu_inicial.mainloop()
But my button doesn't work when I click on it, it only shows the print once in the console when I run the code, I added some prints here in the question:
Problem Picture one
Well it seems that when I use the Lambda function in the button it works and I really would like to know why.
Lambda working button Picture one
I just added to the button the lambda :
botao = Button(menu_inicial, text = "Executar", command=lambda:comando_click("Nova_Mensagem"))
Lambda working button Picture two
Why with lambda it works?
It shoudn't work without lambda too since lambda is basically a anonymous function?
I am extremely curious to understand why it works, thank you all for the help :)
Edit: I would like to thank you guys, now I finally understand what was going on and how Python was working. Thank you all very much :D
When you use () with a function name(func(args)), then it is immediately calling/invoking the function while python is executing the line, you do not want that. You want to ONLY call the function when the button is clicked. tkinter will internally call the function for you, all you have to do is give the function name.
Why use lambda? Think of it as a function that returns another function, your code can be lengthened to:
func = lambda: comando_click("Nova_Mensagem")
botao = Button(menu_inicial, text = "Executar", command=func)
func is the function name and if you want to call it, you would say func(). And when you say command=comando_click("Nova_Mensagem") then command has the value returned by command click(because you call the function with ()), which is None and if I'm not wrong, if the given value is None, it will not be called by tkinter. Hence your function is executed just once because of () and as a result of calling the function, you are assigning the value of the function call(None) before the event loop starts processing the events.
Some other methods:
Using partial from functools:
from functools import partial
botao = Button(.....,command=partial(comando_click,"Nova_Mensagem"))
Using a helper function:
def helper(args):
def comando_click():
print(args)
return comando_click
botao = Button(...., command=helper("Nova_Mensagem"))
IMO, lambdas are the easiest way to proceed with calling a function with arguments.
In this code:
command=comando_click("Nova_Mensagem")
you have called the comando_click function, once, and assigned the result (None) to the command argument. Nothing will happen when command is called (in fact you should get a TypeError exception because None is not callable).
In this code:
command=lambda:comando_click("Nova_Mensagem")
you have not actually called comando_click yet -- you have created a new function (using lambda) that will in turn call comando_click when it is called. Every time the button is clicked, your new function will get called.
If the lambda is confusing, you can do the exact same thing with a def like this:
def button_command():
comando_click("Nova_Mensagem")
...
command=button_command # no ()! we don't want to actually call it yet!
The lambda expression is just an alternative to using def when you want to create a small single-use function that doesn't need a name (e.g. you want to make a function that calls another function with a specific argument, exactly as you're doing here).
The issue is that with comando_click("Nova_Mensagem") you are executing the function. So command=None.
In the second case lambda:comando_click("Nova_Mensagem") is returning a lambda, that internally calls comando_click("Nova_Mensagem").
Fix: just put command=comando_click.
If you want to personalize the lambda with arguments you could write something like this:
def handler(args):
def custom_handler():
print(args)
return custom_handler
botao = Button(menu_inicial, text = "Executar", command=handler("my custom string"))

Lambda function: why do I need a parameter in eventhandlers but not in Button-commands?

In the following case of a Python lambda call, I need to set a parameter for the function to work properly:
name = Entry(self.new_jobtile, width=30)
...
name.bind('<Return>', lambda x:self.create_tile(name.get()))
However, if I use a Button instead, the very same lambda call works without the "x" parameter:
Button(self.new_jobtile, text="OK", command=lambda: self.create_tile(name.get()), width=4, height=2).pack(side=BOTTOM, pady=3, padx=5)
I really don't understand why?
In Python you can create lambda function without arguments:
bar = lambda : 4*2
bar() # 8
I do not know what library are you using but I think that in name.bind second argument should be function with one parameter (like lambda x: or def foo(x):) but in Button constructor argument command should be function with 0 parameters (like lambda : or def foo():).
I have no idea about API of that Entry and Button classes, but here is my guess. Probably bind method of the Entry requires a callback function (your lambda) to has one parameter, in other words, somewhere in the bind method can be a piece of code like this:
def bind(self, an_argument, yourcallback):
yourcallback(something)
You just ignore this parameter, but you can figure out what that bind method passes to your callback:
name.bind('<Return>', lambda x:self.create_tile(str(x) + " " + name.get()))
From the other side, the pack method does not pass a parameter. So your lambda works in that case.

Can I utilize Pyside clicked.connect to connect a function which has a parameter

I want to have a function in main class which has parameters not only self.
class Ui_Form(object):
def clearTextEdit(self, x):
self.plainTextEdit.setPlainText(" ")
print("Script in Textbox is Cleaned!",)
x will be my additional parameter and I want clearTextEdit to be called by click.
self.pushButton_3.clicked.connect(self.clearTextEdit(x))
it does not allow me to write x as parameter in clicked. Can you help me!
Solution
This is a perfect place to use a lambda:
self.pushButton_3.clicked.connect(lambda: self.clearTextEdit(x))
Remember, connect expects a function of no arguments, so we have to wrap up the function call in another function.
Explanation
Your original statement
self.pushButton_3.clicked.connect(self.clearTextEdit(x)) # Incorrect
was actually calling self.clearTextEdit(x) when you made the call to connect, and then you got an error because clearTextEdit doesn't return a function of no arguments, which is what connect wanted.
Lambda?
Instead, by passing lambda: self.clearTextEdit(x), we give connect a function of no arguments, which when called, will call self.clearTextEdit(x). The code above is equivalent to
def callback():
return self.clearTextEdit(x)
self.pushButton_3.clicked.connect(callback)
But with a lambda, we don't have to name "callback", we just pass it in directly.
If you want to know more about lambda functions, you can check out this question for more detail.
On an unrelated note, I notice that you don't use x anywhere in clearTextEdit. Is it necessary for clearTextEdit to take an argument in the first place?

unable to pass information to callback function

I am using Tkinter,python 2.7 and am new to gui programming. I want to pass a string to a callback function in a button.I have tried both partial from functools and lambda functions. So far no luck. So I must be doing something wrong.
def fetch_urls(name):
print name
root=Tk()
aname=StringVar()
E1 = Entry(root, bd =5,textvariable=aname,justify=CENTER,width=20)
E1.grid(row=0,column=1,columnspan=3)
fetchbutton=Button(root,text ="FETCH",command =fetch_urls)
fetchbutton.grid(row=7,column=0)
what I basically want is to send the string in aname ie the text in the entry widget E1 to the function fetch_urls. I have tried the following with no success
1) using lambda
anime=aname.get()
lambdafetch=lambda: fetch_urls(anime)
fetchbutton=Button(root,text ="FETCH",command =lambdafetch)
2)using partial from functools
anime=aname.get()
parfetch=partial(fetch_urls,anime)
fetchbutton=Button(root,text ="FETCH",command =parfetch)
In both cases the string is not passed to the function. I would like to know what I am doing wrong and how to make it work.
def fetch():
fetch_urls(aname.get())
fetchbutton=Button(root,text ="FETCH",command =fetch)
this works(got the idea 15 mins after posting the question). but surely this can't be the only way?
The problem is that with both lambda and partial you are evaluating aname at the time the callback is created, instead of being evaluated at the time it is actually called.
For partial, you would need to modify the fetch_urls function to actually eval the value:
def fetch_urls(aStringVar):
print aStringVar.get()
parfetch = partial(fetch_urls, aname)
For lambda, just have it evaluate the object:
lambdafetch = lambda: fetch_urls(aname.get())
But I tend to not use lambda for callbacks because you might run into scope issues. You may have to use a trick to "capture" the aname object:
lambdafetch = lambda s=aname: fetch_urls(s.get())

Python - returning from a Tkinter callback

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)?
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)
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.
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.

Categories

Resources