简体   繁体   中英

Python & Tkinter - buttons command to set label textvariable issue

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 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, eg 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, ie 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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM