Understanding function call for tkinter button command - python

I am working through a tutorial on tkinter. I adapted some code, with the idea that every time you clicked on the button we'd create a new text label in the row below. What is confusing me is that when I write command=click it works, but when I write command = click() the function is executed a single time at the beginning of the script but not when the button is pressed. The first behaves as I'd expect. The second behaviour confuses me, as I would expect it to raise an error not perform the function once without pressing the button.
from tkinter import *
root = Tk()
position = {"Loc":1}
def click(position=position):
Label(root, text="ta").grid(row=position["Loc"], column=0)
position["Loc"] = position["Loc"] + 1
return
but = Button(root, text="click here to do nothing",command=click())
but.grid(row=0,column=0)
root.mainloop()
Note: I found the answer below more helpful than the answer to the related question here , because it made explicit that arguments are evaluated first before being passed into the function.

When you run a function in Python (like but = Button(root, text="click here to do nothing",command=click())), first all of the arguments get evaluated and then passed to the function. So, you are calling click() and then assigning its return value to the command. If it returns None or something else that Button can handle you will get no error. When you pass just click, you actually tell the function that it is supposed to call that function whenever you click the button.
Some simple code to clarify the issue:
def foo(bar):
print("\tCalling function {} from foo".format(bar))
if bar:
bar()
def fun():
print("\tExecuting fun function")
print("\nPassing function")
foo(fun)
print("\nPassing function return value")
foo(fun())
If you check out the outputs you will notice that they get executed in different order - in the second case, fun is executed first, returns None and than that None value is given to foo as a bar argument.
Passing function
Calling function <function fun at 0x000001A79F8863A0> from foo
Executing fun function
Passing function return value
Executing fun function
Calling function None from foo
As to why there is no error - why would there be?
Both are valid statements. You could have a function that returns other function and pass it to the button that way.

Related

Tkinter: processing button clicks using lambda / eval

The following is the simplest code required to illustrate my question:
def function_one():
pass
def function_two():
pass
def button_clicked(function_name_as_string):
# do stuff common to all button clicks
# here...
# then respond to specific button click by converting the
# supplied function name string into an actual function which runs.
eval(function_name_as_string + "()")
button_one = Button(root,text="Run Function One",
command=lambda: button_clicked("function_one"))
button_two = Button(root,text="Run Function Two",
command=lambda: button_clicked("function_two"))
This is an attempt to make my tkinter code as compact as possible. Here, there are multiple buttons. But rather than write a function to handle each button click, I wanted a single function to handle all button clicks. In particular, that's because there are some operations I want to execute in response to any button click, before responding to the specific button click. At the same time, I wanted to eliminate any lengthy if/elif statement in the button_clicked function, which would be required for the function to know which button had been clicked, and take appropriate action.
In my solution above, when a button is clicked, the command keyword calls my button_clicked function, but also uses lambda to send the name of the function I want to run in response to the button click. The button_clicked function executes the code common to all button clicks, then uses eval to convert the supplied function name (passed as a string) into an actual function that now runs.
So the button_clicked function is not aware of which button was clicked. Rather, it is aware of what function ought to run in response to the specific button click. And this code runs perfectly.
I have never used lambda before, nor have I used eval. I came across both during a long search. But because both are new to me, I am a bit wary. My question is simply this: is the above approach correct and sound? Or are there hidden pitfalls that I may not be aware of? Thanks in advance.
I would argue it's better to pass the actual function than the function name as a string. eval rarely adds any value over other solutions, and mostly just makes the code harder to understand.
def function_one():
pass
def function_two():
pass
def button_clicked(function):
# do stuff common to all button clicks
# here...
# then respond to specific button click by calling
# the specified function
function()
button_one = Button(root,text="Run Function One",
command=lambda: button_clicked(function_one))
button_two = Button(root,text="Run Function Two",
command=lambda: button_clicked(function_two))

Difference between using or not lambda function in python tkinter button command

i have a question when using the Button widget in tkinter. I am new to this.
I noticed that when we use the command in the Button widget, sometimes we call a simple function just like that and sometimes we use lambda function and then we call it. What is the difference?
For example: tk.Button(window, text = "Click Me!", command = myfunction)
tk.Button(win,text="Result",command=lambda: result(en1.get())
Cant we just use it without lambda?
THank you.
Use of lambda:
The parentheses are the main reason that the function gets executed when given as command to a Button without lambda. If the function(which you are passing to the Button as a command) has no parameters(to be passed to itself), then you can simply pass it as a command avoiding the parentheses(). And hence you don't need to use lambda in this case. Like in this Example:command=func.
So using lambda is only necessary when the function has its own parameters(to be passed to itself).Like in this Example:command=lambda:func(a,b,c)
What lambda Does:
When you have to pass arguments to the function itself you have cannot avoid parentheses().
So in the case of buttons, lambda basically delays the execution of the function until the user clicks the button, by creating another function on the spot, which does not get called until the button is actually clicked. Hence the function does not get executed, where it is given as command to the Button.
Any Questions will be answered.

Python Closing Toplevel Window Error

I wanted this code to create a popup error window that destroys itself after 4 seconds but can also be closed via a button.
def error(self):
top = Toplevel()
top.after(4000, lambda: top.destroy())
center_window(300,100, top)
top.title("Error")
Label(top, text="Please enter a valid code", height=3, width=200).pack()
ok = Button(top, text="OK", command=top.destroy)
ok.pack()
ok.bind("<Return>", lambda a: top.destroy())
ok.focus_set()
I have run the code and it works fine 90% of the time except sometimes it throws this error:
TypeError: <lambda>() takes exactly 1 argument (0 given)
I have done research that says it is Tkinters threading. I am not sure if this is my issue but when I take out this line of code:
top.after(4000, lambda: top.destroy())
It seems to work. If anyone could help me, I have taught myself what I know of python, so I am sure there are holes in my learning. I think I may need to somehow use the main thread of execution to destroy this window, or otherwise create my own custom widget. Any help is appreciated.
When using after or bind, you don't need to use lambda.
Instead, for example, use:
top.after(4000, top.destroy)
which references the function top.destroy directly.
You can directly bind the function to be called instead of using a lambda:
top.after(4000, top.destroy)
...
ok.bind("<Return>", top.destroy)
You would use a lambda, for instance, if you needed to pass arguments to the function; this is not the case here.

Tkinter bind not working when passed root.destroy directly

I'm fairly new to python and tkinter. I'm working with python 2.7 and tkinter 8.5.
I'm trying to exit my app when the escape key is pressed, and I ran into some odd behaviour:
When I pass root.destoy as the argument to bind(), the app does nothing:
root = Tk()
...
root.bind('<Escape>', root.destroy)
But if I define a function that calls root.destroy() and pass that as an argument to bind, everything works as expected:
def exit_app():
root.destroy()
root.bind('<Escape>', exit_app)
It also works if I pass a lambda like this:
root.bind('<Escape>', lambda f: root.destroy())
Can anyone explain what's happening here?
Thanks
When you bind a command to an event, that command is passed an argument which is is an object that represents the event. root.destroy does not accept any arguments, therefore it is throwing an error instead of running.
That is why your lambda works: your lambda accepts an argument (oddly, named f).
You claim in your question that it works with this exact function definition:
def exit_app():
root.destroy()
I find that impossible to believe, for the same reason described above.

python delay function call [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 7 years ago.
I want to delay a function call. (Or in my mind: python is executing the function in the wrong order). In the below example i could write instead of bf(arg) the two functions bf1 and bf2 and it does work as expected: The function is called whenever the button is pressed. But if i include the arguments in the function the function call is executed only once. Returning the function itself doesn't change the behaviour.
Can you please take a look at it and give me a hint where my logic or understanding of python is wrong.
from tkinter import *
def bf(str):
print(str)
# return print(str) #alternative(same behaviour)
main=Tk(screenName='screen',baseName='base')
button1=Button(master=main,text='b1',command=bf("b1"))
button2=Button(master=main,text='b2',command=bf("b2")) # runs once (and runs here)
button1.pack()
button2.pack()
mainloop()
print('end')
--google and stackoverflow search only return things like delay function call for 1 a specific time interval This is not what i am searching for. :-(
The issue is that you are calling the function when you create the buttons, instead of passing a callable that TK will call when the button is clicked. Normally you would just pass the function itself:
button1=Button(master=main, text='b1', command=bf)
but since you want to pass arguments to bf you will need to wrap it in a lambda:
button1=Button(master=main, text='b1', command=lambda: bf('b1'))
What you do in that line is that you don't pass the function but execute it:
button1=Button(master=main,text='b1',command=bf("b1"))
You could either include only the function name, but then you can't pass a parameter to the function:
button1=Button(master=main,text='b1',command=bf)
or you could make use of lambda:
button1=Button(master=main,text='b1',command=lambda:bf("B1"))

Categories

Resources