0

The code below creates 10 buttons. When user clicks the button, we need to know which button was clicked.

import tkinter as t

def click_button(number):
    print("you clicked %d" % number)

root = t.Tk()

for number in range(0, 10):
    btn = t.Button(root, text=number, padx=15, command=lambda: click_button(number))
    btn.grid(row=0, column=number)

root.mainloop()
  1. When I click any button, I always get 9. Why is that?
  2. Is there a better/alternative way to address this problem?
dhiman
  • 385
  • 3
  • 9

2 Answers2

1

It appears that the closure over number is by reference given a simple loop test, however I'm not sure this is correct behavior.

This works using partial which I have no doubt captures the value of number at the time of constructing the partial:

import functools as fc
import tkinter as t


def click_button(number):
    print("you clicked %d" % number)


root = t.Tk()

for number in range(0, 10):
    btn = t.Button(
        root,
        text=number,
        padx=15,
        command=fc.partial(click_button, number))
    btn.grid(row=20, column=number)

root.mainloop()
Frank C.
  • 7,758
  • 4
  • 35
  • 45
  • @dhiman Not sure either, I would expect the lambda declaration closes over (`closure`) number at the time – Frank C. Apr 06 '20 at 08:43
1

You need to pass number as a lambda parameter. Try:

command=lambda param = number : click_button(param)

Some clarification about the way lambda function "captures" local variable can be found here: What do (lambda) function closures capture? and here: https://docs.python.org/3/faq/programming.html#why-do-lambdas-defined-in-a-loop-with-different-values-all-return-the-same-result

In a nutshell, by passing number as a parameter to the lambda, you ensure a copy of number will be performed for iterated number value. If you do not pass it as parameter, all lambda functions "number" point to the same variable ( loop number) which is evaluated only when the lambda function is called. At the time it is called number value is "9". This is very different from C++ where the scope of a "for loop" variable is the loop itself. In python the variable of the loop exists as long as someone (here the lambdas) references it.

Jean-Marc Volle
  • 3,113
  • 1
  • 16
  • 20