Tkinter中为何Button的command与bind绑定右键事件的lambda行为不一致?
核心差异:command与bind的回调入参规则完全不同
不管你写的lambda看起来多像,tk.Button的command参数和bind方法绑定的事件回调,触发时的传参逻辑是完全不一样的,这是所有行为不一致的根本原因:
- 触发
command绑定的回调时,Tkinter不会主动传入任何额外的位置参数 - 触发
bind绑定的事件回调时,Tkinter一定会把当前的Event事件对象,作为第一个位置实参塞给你的回调函数
三种错误写法的具体触发原因
- 原代码报TypeError的写法:
buttons_list[z].bind("<Button-3>", lambda i=z: print(myList[i]))
触发右键事件时,Tkinter自动把Event对象当第一个参数传进来,直接覆盖了i原本在定义时设的默认值z,最后用来取列表的索引变成了Event对象,自然抛出“列表索引必须是整数”的错误。 - 不管点哪个按钮都输出
no的写法:buttons_list[z].bind("<Button-3>", lambda i: print(myList[z]))
这里的lambda没有把循环变量z设为参数默认值,所有在循环里生成的lambda都会延迟绑定z本身,等for循环跑完,z的值停在2,所以所有回调里取的都是myList[2]也就是"no";此时Tkinter传进来的Event对象被赋值给了参数i,只是没被用到而已。 - 程序启动就自动输出列表内容的写法:
buttons_list[z].bind("<Button-3>", print(myList[z]))bind的第二个参数要求传一个“可以被调用的函数”,但这里你在定义绑定时就直接执行了print(myList[z]),把print的返回值(也就是None)传给了bind,实际根本没绑上有效的响应函数。自然只有程序启动跑循环的时候会把三个值按顺序打出来,右键点按钮完全没反应。
为什么command里的同句式lambda没出问题
你在按钮初始化时写的tk.Button(..., command=lambda e=z:print(myList[e]))跑起来完全正常,是因为command回调触发时不会主动传任何参数,e会一直用定义时绑定的默认值z,每个按钮的回调都对应自己的索引,自然能正确输出。
但同样的写法放到bind里,就会被Tkinter强制传进来的Event对象把默认值覆盖掉,从根上就不兼容。
bind的可用写法
如果要在循环里用bind绑事件,只需要给Tkinter自动传的Event参数留好位置,再把循环变量用默认参数的形式固定下来就行,两种常见写法都可以:
# 显式定义event形参接收到Tkinter传进来的事件对象 buttons_list[z].bind("<Button-3>", lambda event, idx=z: print(myList[idx])) # 用*args收集所有传入的参数,避免和自定义默认参数冲突 buttons_list[z].bind("<Button-3>", lambda *args, idx=z: print(myList[idx]))
补充:Python里lambda的闭包是延迟绑定的——如果不在定义lambda时就把循环变量的值用默认参数固定住,所有循环生成的回调都会统一引用循环结束后的变量最终值,这是写循环绑定事件时最容易踩的通用坑。
内容的提问来源于stack exchange,提问作者CCyanide
相关产品推荐
相关产品推荐

