Understanding Python Lambda behavior with Tkinter Button [duplicate] - python

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"))

Related

What's the importance of lambda functions in tkinter?

Can anyone explain to me the importance of the lambda function when creating interface with Tkinter?
I am building a super simple interface to get familiar with Tkinter and I wanted to make so that when I press the "Return" key on keyboard, it would have the same effect as dragging the mouse to the "Submit" button on the screen.
I had some problems with it because nothing seemed to work. That's what I was doing:
self.master.bind("<Return>", self.concluir_return)
Where self.concluir_return is the function responsible for making what I want after pressing the "Submit" button. But it was giving me a TypeError:
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.6/tkinter/__init__.py", line 1705, in __call__
return self.func(*args)
TypeError: concluir_return() takes 1 positional argument but 2 were given
Which I don't understand at all, since only one argument was being given (self)
But all was solved when I looked into web and modified the line of code with the lambda function.
self.master.bind("<Return>", lambda event: self.concluir_return())
It works perfectly, but I don't quite understand what is happening. I would appreciate if someone could explain.
Also, I hope this is not too generic of a question.
Can anyone explain to me the importance of the lambda function when creating interface with Tkinter?
Arguably, they aren't important at all. They are just a tool, one of several that can be used when binding widgets to functions.
The problem with the binding in your question is due to the fact that when you use bind to bind an event to a function, tkinter will automatically pass an event object to that function you must define a function that accepts that object.
This is where lambda comes in. The command needs to be a callable. One form of a callable is simply a reference to a function such as the one you're using (eg: command=self.concluir_return). If you don't want to modify your function to accept the parameter you can use lambda to create an anonymous function -- a callable without a name.
So, for your specific case, you can define a lambda that accepts the argument, and then the lambda can call your function without the argument.
But all was solved when I looked into web and modified the line of code with the lambda function.
self.master.bind("<Return>", lambda event: self.concluir_return())
This works because the code is effectively the same as if you did this:
def i_dont_care_what_the_name_is(event):
self.concluir_return()
self.master.bind("<Return>", i_dont_care_what_the_name_is)
As you can see, lamda isn't required, it's just a convenient tool that lets you create a simple function on the fly that calls another function.
The bind method takes two arguments, sequence and handler, and will call f(event) when the specified event occurs.
In your case, concluir_return wasn't expecting any argument other than self, so your code raised an error when it was called with event.
The lambda function you used is the equivalent of:
def f(event):
return concluir_handler()
so it bypasses the problem by just ignoring the event argument.
Another way of doing this would be to add an argument to concluir_return.

Function executes correctly as a tkinter button command but not in the script

This is my first question on StackOverflow. So far lurking was enough to solve all my problems.
I'm a python newbie and I don't fully understand the meaning behind 'self' yet.
I defined a function (not a method. It's not inside a class) as
def pcal_thresh(self):
p_th = p_thresh.get()
print('p_th')
I am trying to use it in 2 separate conditions. First as a command for Tkinter
p_thresh = tk.Scale(calibration, from_=255, to=1, length=int(y_height*1.2), command=pcal_thresh)
Second, inside another function
def confirm():
if not top_distance == 0:
pcal_thresh()
In this exact configuration the function "pcal_thresh()" executes correctly as a Tkinter command, but not inside another function. If I remove 'self' from the declaration, it's the opposite. Works fine when used inside a function, but not as a Tkinter command. What can be the issue here?
self does not have a default value, so even if you don't use it, you still need to provide a value when you call pcal_thresh. As a callback, it receives the new scale value when called.
Either provide a dummy argument
def confirm():
if not top_distance == 0:
pcal_thresh(None)
or provide a default value:
def pcal_thresh(self=None):
p_th = p_thresh.get()
print('p_th')

difference between a function with and without parenthesis in python

I have two questions concerning python assuming the code below:
Why it is not possible to pass a function with parentheses or parameters to add_command?
What shall I do if my CreateWindow function has to take parameters?
Here's the line of code:
filemenu.add_command(label="update...", command=CreateWindow)
Doing command=CreateWindow(some_argument) will cause CreateWindow to be executed immediately, and whatever it returns will be used as the parameter for command. Python isn't smart enough to guess that you want CreateWindow to be the callback and not its return value.
Use a lambda expression: filemenu.add_command(label="update...", command=lambda: CreateWindow(some_arguments, go_here))
This is effectively equivalent to:
def f():
CreateWindow(some_arguments, go_here)
filemenu.add_command(label="update...", command=f)
... But much shorter.

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

Categories

Resources