ekyedekot
ekyedekot

Reputation: 3

Calling multiple functions using buttons by loop

I'm trying to run a (GUI) program in Python (Tkinter) which should work as follows:

A loop generates five (or n in general, I'm just trying to do it for n=5 for now) button, and clicking on the i'th button prints out i. Here's what I did:

def func(i):
    print i
for i in range(5):
        buttons[i]= Button(but, text= "%s" %str(inputs[i]), command= lambda: func(i+1))
        buttons[i].grid(row= i+2, column= 0)

Problem is, clicking on all of the buttons prints 5, so somehow func(5) is assigned to all of the buttons. Surprisingly, doing the following works out.

buttons[0].config(command= lambda: func(1))
buttons[1].config(command= lambda: func(2)) 
buttons[2].config(command= lambda: func(3))
buttons[3].config(command= lambda: func(4))
buttons[4].config(command= lambda: func(5))

As I'm trying to do it in general for any number of inputs, there's no other way to do this without looping. Could anyone please help me how to fix that? Thanks!

Upvotes: 0

Views: 244

Answers (2)

Julian
Julian

Reputation: 2654

The issue is one of variable scope. The variable i is in your global scope here, so the lambdas are all using that same variable with the value it has at the time of button press (which is 5).

The simplest fix would be to replace your lambda with something like lambda j=i+1: func(j).

This declares a new variable (j) which is defined only in the scope of the value and is assigned the value of i+1 at the time the lambda is declared.

Upvotes: 1

Terry Jan Reedy
Terry Jan Reedy

Reputation: 19209

Like many others, you have fallen victim to the lambda illusion. Even though you are creating 5 function objects, they are all identical since all 5 have the same body, and the name 'i' in the body is not evaluated until any is called. Your code is equivalent to

def func(j):
    print j
def cmd():
    func(i+1)
for i in range(5):
        buttons[i] = Button(but, text="%s" % str(inputs[i]), command=cmd)
        buttons[i].grid(row= i+2, column= 0)

Here it is obvious that you are attaching the same command to each button, and that pressing any button would use the final value of i, which is 4. To get 5 different functions, delete def cmd and replace command=cmd with either of the following:

command=lambda j=i: func(j+1)
command=lambda j=i+1: func(j)

I strongly recommend using different parameter names in the different functions, and especially not reusing the loop variable name as the parameter name for the function defined in the loop. In 3.x, you could delete func and use

command=lambda j=i+1: print(j+1)

Upvotes: 2

Related Questions