0

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!

2
  • 2
    I don't think you're actually using the command parameter on the grid() method... Commented Oct 24, 2014 at 18:15
  • Whoops; fixed. Thanks! That's indeed not what I did in my original code. Commented Oct 24, 2014 at 18:43

2 Answers 2

2

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)
Sign up to request clarification or add additional context in comments.

Comments

1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.