Python Tkinter Button命令异常:动态创建按钮总是选中最后一个栈
问题描述
我正在用Python开发一款名为Nim的游戏,这里的stack等同于heap。之前固定栈(堆)数量的代码运行完全正常:
class Game: def __init__(self): self.pack_kwargs = {'fill' : BOTH, 'expand' : True, 'padx' : ['0', '0'], 'pady' : ['0', '0']}
def player1(self): def chooseStack(stack): self.stack = stack window_player1_stack.destroy() btn1 = Button(window_player1_stack, text = '1', bd = '2', command = lambda: chooseStack(0)) btn2 = Button(window_player1_stack, text = '2', bd = '2', command = lambda: chooseStack(1)) btn3 = Button(window_player1_stack, text = '3', bd = '2', command = lambda: chooseStack(2)) if self.stacks[0]: btn1.pack(**self.pack_kwargs) if self.stacks[1]: btn2.pack(**self.pack_kwargs) if self.stacks[2]: btn3.pack(**self.pack_kwargs)
但我希望支持任意数量的栈,改为动态创建按钮后出现异常:点击按钮总是选中最后一个栈,而非点击的那个栈。代码如下:
for i in range(len(self.stacks)): Button(window_player1_stack, text = f'{i+1}', bd = '2', command = lambda: chooseStack(i)).pack(**self.pack_kwargs)
我尝试了以下写法,问题依然存在:
buttons = [] for i in range(len(self.stacks)): button = Button(window_player1_stack, text = f'{i+1}', bd = '2', command = lambda: chooseStack(i)) buttons.append(button) if self.stacks[0]: buttons[0].pack(**self.pack_kwargs) if self.stacks[1]: buttons[1].pack(**self.pack_kwargs) if self.stacks[2]: buttons[2].pack(**self.pack_kwargs)
解决方案
问题根源
这是Python lambda闭包的延迟绑定特性导致的:循环中的lambda不会立刻捕获当前的i值,而是在按钮被点击时才去读取i的当前值——此时循环已经结束,i停留在最后一次循环的数值,所以所有按钮都会传入同一个(最后一个)栈索引。
两种修复方式
方式1:给lambda添加默认参数
通过默认参数强制捕获当前循环的i值,避免延迟绑定:
for i in range(len(self.stacks)): # 用默认参数stack=i锁定当前迭代的i值 Button(window_player1_stack, text=f'{i+1}', bd='2', command=lambda stack=i: chooseStack(stack)).pack(**self.pack_kwargs)
方式2:使用functools.partial
借助functools.partial固定函数参数,明确绑定当前i:
from functools import partial for i in range(len(self.stacks)): # 用partial将chooseStack的参数固定为当前i btn = Button(window_player1_stack, text=f'{i+1}', bd='2', command=partial(chooseStack, i)) btn.pack(**self.pack_kwargs)
两种方法都能让每个按钮的command绑定对应循环迭代时的i值,点击时即可传入正确的栈索引。
内容的提问来源于stack exchange,提问作者Roman
相关产品推荐
相关产品推荐

