3

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

3 Answers3

1

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

Samwise
  • 68,105
  • 3
  • 30
  • 44
  • 1
    _"..in fact you should get a TypeError exception because None is not callable.."_: I do not think `tkinter` will bother calling a `NoneType` object anyway – Delrius Euphoria Dec 18 '21 at 19:38
  • 1
    seems like an antifeature in tkinter imo (it should let you know that you typo'd your button definition) but I've never used it :) – Samwise Dec 18 '21 at 19:44
  • 1
    @Samwise: it's not an antifeature. There are reasons you might want or need to set the command to `None`. – Bryan Oakley Dec 18 '21 at 21:16
1

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"))
estebarb
  • 467
  • 5
  • 12
1

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.

Delrius Euphoria
  • 14,910
  • 3
  • 15
  • 46