Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
148 views
in Technique[技术] by (71.8m points)

python - Passing argument to a function via a Button in Tkinter, starnge behaviour in loop

I am currently trying to fix a bug in the python book "Python next step" which the author has not fixed and left a comment in the code : "Fix later"

My first solution failed but the second solution succeeded by removing the loop. Problem is I cannot figure out why the first solution fails!

Solution 1:

When a user clicks the button in the calculator made in Tkinter using a loop, Button object and a function called click the calculator just prints a capital C from the lambda argument.The code is commented by chevrons near the offending cod, at the points I am talking about.

solution 2:

Remove the loop that generated buttons and hand code each button repetitively!This works and as Brian kernighan suggest: getting it to work first before making it efficient!

code:

# myCalculator3_final.py

from Tkinter import *
from decimal import *

# key press function:
def click(key):
        display.insert(END, key)


##### main:
window = Tk()
window.title("MyCalculator")

# create top_row frame
top_row = Frame(window)
top_row.grid(row=0, column=0, columnspan=2, sticky=N)

# use Entry widget for an editable display
display = Entry(top_row, width=45, bg="light green")
display.grid()

# create num_pad_frame
num_pad = Frame(window)
num_pad.grid(row=1, column=0, sticky=W)

# This method of passing an argument to click work! Loop removed and buttons hand code
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#-------------------------------------------------------------------------
# create num_pad buttons passing an argument to the command function click
#-------------------------------------------------------------------------   
seven = Button(num_pad, text="7", width=5, command=lambda :click("7"))
seven.grid(row=0,column=0)
eight = Button(num_pad, text="8", width=5, command=lambda :click("8"))
eight.grid(row=0,column=1)
nine= Button(num_pad, text="9", width=5, command=lambda :click("9"))
nine.grid(row=0,column=2)
four= Button(num_pad, text="4", width=5, command=lambda :click("4"))
four.grid(row=1,column=0)
five= Button(num_pad, text="5", width=5, command=lambda :click("5"))
five.grid(row=1,column=1)
six= Button(num_pad, text="6", width=5, command=lambda :click("6"))
six.grid(row=1,column=2)
one= Button(num_pad, text="1", width=5, command=lambda :click("1"))
one.grid(row=2,column=0)
two= Button(num_pad, text="2", width=5, command=lambda :click("2"))
two.grid(row=2,column=1)
three= Button(num_pad, text="3", width=5, command=lambda :click("3"))
three.grid(row=2,column=2)
zero= Button(num_pad, text="0", width=5, command=lambda :click("0"))
zero.grid(row=2,column=0)
#---------------------------------------------------------------------------   



# calculate the row, column for button

# create operator_frame
operator = Frame(window)
operator.grid(row=1, column=1, sticky=E)

operator_list = [
'*', '/',  
'+', '-',
'(', ')',
'C' ]

# The authors code and I have added  the same lambda function as above but 
#it just prints out capital C
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
r = 0
c = 0
for btn_text in operator_list:
    Button(operator, text=btn_text, width=5, command=lambda: click(btn_text)).grid(row=r,column=c)
    c = c+1
    if c > 1:
        c = 0
        r = r+1


##### Run mainloop
window.mainloop()

Question:

Why does the lambda calling method to click passing an argument not work in the loop and only display a C but if I remove the loop it works!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

On this line:

Button(operator, text=btn_text, width=5, command=lambda: click(btn_text)).grid(row=r,column=c)

The value of btn_text within the lambda will not become frozen with its current value. Instead, when btn_text changes in the next iteration of the loop, the value it evaluates to in the lambda will also change. This means that all of your Buttons effectively have a click('C') command, since 'C' is the final value of btn_text.

You can do:

command=lambda text=btn_text: click(text)

Or

command=(lambda text: lambda: click(text))(btn_text)

text will capture the current value of btn_text, and not change afterwards. Your commands will get called with the proper arguments.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...