Pasando el argumento a una función a través de un botón en Tkinter, se inicia el comportamiento en bucle

Actualmente estoy tratando de corregir un error en el libro de Python “Python next step” que el autor no corrigió y dejó un comentario en el código: “Solucionar más tarde”

Mi primera solución falló, pero la segunda solución tuvo éxito al eliminar el bucle. El problema es que no puedo entender por qué falla la primera solución!

Solución 1:

Cuando un usuario hace clic en el botón en la calculadora hecha en Tkinter usando un bucle, el botón Button y una función llamada clic en la calculadora simplemente imprimen una C mayúscula a partir del argumento lambda. estoy hablando de

solución 2:

¡Elimine el bucle que generó los botones y codifique manualmente cada botón! Esto funciona y, como sugiere Brian Kernighan: ¡haga que funcione primero antes de hacerlo eficiente!

código:

# 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() 

Pregunta:

¿Por qué el método de llamada lambda para hacer clic en pasar un argumento no funciona en el bucle y solo muestra una C, pero si elimino el bucle funciona?

En esta línea:

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

El valor de btn_text dentro de la lambda no se congelará con su valor actual. En cambio, cuando btn_text cambia en la próxima iteración del bucle, el valor que evalúa en la lambda también cambiará. Esto significa que todos sus botones tienen efectivamente un comando de click('C') , ya que 'C' es el valor final de btn_text .

Tu puedes hacer:

 command=lambda text=btn_text: click(text) 

O

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

text capturará el valor actual de btn_text , y no cambiará después. Sus comandos serán llamados con los argumentos adecuados.