from tkinter import *
F=Tk()
i=1
while i<10:
newButton = Button(F,text="Show Number",command=lambda:showNumber(i))
newButton.pack(side=TOP)
i+=1
def showNumber(nb):
print(nb)
F.mainloop()
所有按钮返回10.为什么?
我想按钮1返回1,按钮2返回2 ...
非常感谢你帮助我
答案 0 :(得分:6)
你的匿名lambda
函数可以作为闭包(正如@abernert指出的那样,它们在Python的情况下不是实际上闭包) - 它们“关闭”变量i
,稍后再提及。但是,他们不会在定义时查找值,而是在调用时,这是在整个while
之后的某个时间循环结束(此时,i
等于10)。
要解决此问题,您需要将i
的值重新绑定到lambda要使用的其他内容。你可以通过多种方式实现这一目标 - 这是一个:
...
i = 1
while i < 10:
# Give a parameter to the lambda, defaulting to i (function default
# arguments are bound at time of declaration)
newButton = Button(F, text="Show Number",
command=lambda num=i: showNumber(num))
...
答案 1 :(得分:5)
Python常见问题解答中解释了这一点:Why do lambdas defined in a loop with different values all return the same result?。
引用常见问题解答答案:
这是因为x不是lambdas的本地,而是在外部作用域中定义,并且在调用lambda时访问它 - 而不是在定义它时...
为了避免这种情况,你需要将值保存在lambdas的本地变量中,这样它们就不依赖于全局值......
换句话说,您的新功能不会存储{em>值i
,而是存储变量i
。它们都存储相同的变量i
,它在循环结束时具有值10
。实际上,如果您在i = 'spam'
之前添加F.mainloop()
,您会看到现在所有按钮都打印出字符串spam
而不是数字。
当您尝试创建可能影响其定义环境的闭包函数时,这非常有用。*但是当您不尝试这样做时,这可能会妨碍。
最简单的方法是使用带有默认值的参数。默认值不包含变量;只是在定义函数时计算的值。所以:
newButton = Button(F,text="Show Number", command=lambda num=i: showNumber(num))
*请注意,在这种情况下,实际上并没有涉及任何闭包,因为i
是全局的,而不是封闭范围中的本地。但实际上,这只是因为Python对全局变量有特殊处理,并且不需要闭包;从概念上讲,如果您认为存在问题,除非您开始查看__closure__
或__code__
属性,否则不会遇到任何麻烦。