2

I try to create buttons in tkinter using a loop.

def bulkbuttons():
    i = 0
    x = 0
    buttons=[]
    while x < 10:
        buttons.append('button' + str(x))
        x+=1
    while i < 10:
        buttons[i]=Button(window,text=str(i), width=12,borderwidth=1, relief='raised', bg='#134f5c', fg='#FFFFFF', command=(lambda: justprint(i)))
        buttons[i].grid(row=i, column=2)
        i += 1

The justprint function prints i.

def justprint(i):
    print(i)

The buttons show the correct numbers. But the justprint function only prints the last number, 10, no matter which button I click.

What did I do wrong? I want to click a button and then use the number of the button as a parameter for some functions.

mn_wy
  • 127
  • 6
  • 3
    After thinking about it. I believe the lambda is binding the argument similar to how a default argument gets bound. Try `lambda i=i: printer(i)` – TheLazyScripter Aug 13 '20 at 05:58
  • 2
    Indeed! For loops and closures can produce some counterintuitive results, since `i` is getting mutated, not replaced. The default argument solution suggested by TheLazyScripter should do the trick. – Silvio Mayolo Aug 13 '20 at 06:00

1 Answers1

0

You can pass i as a default argument in the lambda function. Alternatively you can use the partial function from functools:

from functools import partial 

def bulkbuttons():
    # Put all formatting related parameters of the Button constructor in a dictionary
    # Using ** will pass dict values to 
    formatting = {'width': 12, 
                  'borderwidth': 1,
                  'relief': 'raised',
                  'bg': '#134f5c', 
                  'fg': '#FFFFFF'}

    for i in range(10):
        buttons[i]=Button(window,
                          text=str(i),
                          **formating,                      
                          command=partial(justprint,i))
        buttons[i].grid(row=i, column=2)

Notes:

  • Your first while loop can be expressed as this pretty and concise list comprehesion:
buttons = ['button' + str(x) for x in range(10)]

Try using this notation when possible, since they will save you typing time and is far more readable.

  • Your second while loop overwrites the list created in the first one, so there is no need to do the first while loop.

  • I took the liberty of placing all your formating related parameters for the Button constructor in a dictionary, you can pass them all at once passing **formating to the constructor. You can place these parameters in your global scope (outside functions) and define models for every type of button, saving you some time and making the code more readable.

  • If you have a fixed number of iteration is a loop, use for i in range(n) instead of a while, it will avoid some infinite loops when you forget the i+=1