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()
This is a common pitfall with declaring a
lambda
in a loop. The variablei
is evaluated when thelambda
is called, not when it is defined, thus all the functions end up using the value that was assigned toi
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:Also note, that if you do
bi = Button(...).grid(...)
,bi
will be assigned the result of thegrid
function, i.e.None
. You do not needbi
in this case anyway, so it does not matter much, but that's another common problem, so better don't develop that habit.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:
Let's see what this does:
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.