1

I am playing around with Tkinter and building a calculator from the base up. To try and understand as learn as much as possible about the event and the library as I can while I go along.

Right now I am at a point where I simply want the buttons to pass the value on the button to the label at the top.

I've used a for loop to create most of the buttons to avoid redundant code but now the only value being passed onto the textvariable in the label is the last item, '.', in my buttons list and I am not sure why that is. Can someone help?

code below:


from Tkinter import *
import Tkinter as tk

# main window
root = Tk()
root.title('Calculator')

# button set
buttons = ['1','2','3','4','5','6','7','8','9','0','+','-','/','*','.']

sum_value = StringVar()

# output window
output_window = tk.Label(root, textvariable=sum_value, width=20, height=2).grid(row=0, columnspan=3, sticky=(E,W))

# button creation
r=1
c=0

for i in buttons:
    if c < 2:
        bi = tk.Button(root, text = i, command = lambda: sum_value.set(i), padx = 5, pady = 3).grid(row = r, column = c, sticky = (N,S,E,W))        
        c += 1
    else:
        bi = tk.Button(root, text = i, command = lambda: sum_value.set(i), pady = 3).grid(row = r,column = c,sticky = (N,S,E,W))
        r  += 1
        c = 0

# clear and equals button
clear = tk.Button(root,text='=',padx = 5, pady=3).grid(row=6,column=0,sticky=(N,S,E,W))
clear = tk.Button(root,text='CLEAR',padx = 5, pady=3).grid(row=6,column=1, columnspan = 2,sticky=(N,S,E,W))

root.mainloop()
daansteraan
  • 103
  • 1
  • 5

2 Answers2

1

This is a common pitfall with declaring a lambda in a loop. The variable i is evaluated when the lambda is called, not when it is defined, thus all the functions end up using the value that was assigned to i in the final iteration of the loop. There are some ways to fix this, e.g. to use a parameter and assign it a default-value, that will be evaluated when the function is defined.

In your case, you can change your lambdas to this form, then it should work:

bi = tk.Button(..., command=lambda i=i: sum_value.set(i), ...)
bi.grid(...)

Also note, that if you do bi = Button(...).grid(...), bi will be assigned the result of the grid function, i.e. None. You do not need bi in this case anyway, so it does not matter much, but that's another common problem, so better don't develop that habit.

tobias_k
  • 81,265
  • 12
  • 120
  • 179
  • @SDilmac If it was running also for the OP, he/she would not have asked the question, don't you think? – nbro Aug 21 '15 at 14:04
  • @tobias_k Great! thanks man - legendary! the bi's were leftovers from something I tried earlier, meant to take it out :-P.. – daansteraan Aug 21 '15 at 14:14
  • @daansteraan Another thing you could do is _binding_ the `""` event with the call to a function that sets the text of the label. This function that is called needs to specify a parameter for the event object, and through this parameter, which will point to the event object, you can access the widget (and therefore its properties, in your case the text of the buttons) on which the event occurred, and then modify the label accordingly. If everything I said is strange for you, just ignore it for now, but you will probably have to understand the concepts of "events and bindings" – nbro Aug 21 '15 at 14:20
0

This question provides a good explanation for your predicament.

There are two ways around this:

(1) The default argument method as described above.

(2) Creation of a new scope each time you create the lambda:

for i in buttons:
    if c < 2:
        bi = tk.Button(root, text = i, command = (lambda a: lambda : sum_value.set(a))(i), padx = 5, pady = 3).grid(row = r, column = c, sticky = (N,S,E,W))        
        c += 1
    else:
        bi = tk.Button(root, text = i, command = (lambda a: lambda : sum_value.set(a))(i), pady = 3).grid(row = r,column = c,sticky = (N,S,E,W))
        r  += 1
        c = 0

Let's see what this does:

(lambda a: lambda : sum_value.set(a))(i)

The outer lambda function is called with the parameter i, which creates a closure for the inner lambda function. A closure is an object that remembers values in enclosing scopes. The inner lambda function will have access to the value that i had at the time the lambda function was declared.

Community
  • 1
  • 1
deborah-digges
  • 1,165
  • 11
  • 19