为什么我的按钮命令在创建时立即执行,而不是在点击时?
我的代码是:
from Tkinter import *
admin = Tk()
def button(an):
print(an)
print('het')
b = Button(admin, text='as', command=button('hey'))
b.pack()
mainloop()
这个按钮不管用,它会在没有我点击的时候自己打印出'hey'和'het'各一次,然后当我按下按钮时什么也不发生。
5 个回答
示例图形界面:
假设我有这样一个图形界面:
import tkinter as tk
root = tk.Tk()
btn = tk.Button(root, text="Press")
btn.pack()
root.mainloop()
当按钮被按下时会发生什么
你会看到,当 btn
被按下时,它会调用 它自己的 函数,这个函数和下面例子中的 button_press_handle
非常相似:
def button_press_handle(callback=None):
if callback:
callback() # Where exactly the method assigned to btn['command'] is being callled
这里:
button_press_handle(btn['command'])
你可以简单理解为 command
选项应该设置为我们想要调用的方法的引用,这和 button_press_handle
中的 callback
类似。
当按钮被按下时调用方法(一个 回调)
没有 参数
所以如果我想在按钮被按下时 print
一些东西,我需要设置:
btn['command'] = print # default to print is new line
请注意 print
方法后面没有 ()
,这意味着:"这是我希望你在按下时调用的方法的名字但不要立刻调用它。"不过,我没有给 print
传递任何参数,所以它会打印出在没有参数时的内容。
有 参数
现在如果我想在按钮被按下时给 要调用的方法 传递参数,我可以使用匿名函数,这可以通过 lambda 语句来创建,像下面这样用于 print
内置方法:
btn['command'] = lambda arg1="Hello", arg2=" ", arg3="World!" : print(arg1 + arg2 + arg3)
当按钮被按下时调用 多个 方法
没有 参数
你也可以使用 lambda
语句来实现这一点,但这被认为是不好的实践,所以我不会在这里提到。好的做法是定义一个单独的方法 multiple_methods
,这个方法会调用你想要的方法,然后将它设置为按钮按下时的回调:
def multiple_methods():
print("Vicariously") # the first inner callback
print("I") # another inner callback
有 参数
为了给调用其他方法的方法传递参数,再次使用 lambda
语句,但首先:
def multiple_methods(*args, **kwargs):
print(args[0]) # the first inner callback
print(kwargs['opt1']) # another inner callback
然后设置:
btn['command'] = lambda arg="live", kw="as the" : a_new_method(arg, opt1=kw)
从回调中返回对象
还要注意的是,callback
不能真正 return
,因为它只是在 button_press_handle
内部被调用,使用的是 callback()
,而不是 return callback()
。它确实会 return
,但 不 在那个函数之外。因此,你应该更倾向于 修改 当前作用域内可以访问的对象。
带有 全局 对象修改的完整示例
下面的例子将在每次按钮被按下时调用一个方法,改变 btn
的文本:
import tkinter as tk
i = 0
def text_mod():
global i, btn # btn can be omitted but not sure if should be
txt = ("Vicariously", "I", "live", "as", "the", "whole", "world", "dies")
btn['text'] = txt[i] # the global object that is modified
i = (i + 1) % len(txt) # another global object that gets modified
root = tk.Tk()
btn = tk.Button(root, text="My Button")
btn['command'] = text_mod
btn.pack(fill='both', expand=True)
root.mainloop()
你需要创建一个没有参数的函数,这样你就可以把它当作命令来使用:
b = Button(admin, text='as', command=lambda: button('hey'))
可以查看这份文档中的“传递参数给回调”部分。
考虑一下这段代码:
b = Button(admin, text='as', command=button('hey'))
它的功能和这段代码完全一样:
result = button('hey')
b = button(admin, text='as', command=result)
同样,如果你这样创建一个绑定:
listbox.bind("<<ListboxSelect>>", some_function())
... 它和这段代码也是一样的:
result = some_function()
listbox.bind("<<ListboxSelect>>", result)
command
选项需要一个函数的引用,简单来说就是你需要传递函数的名字。要传递引用,你只需要用名字,不要加括号或参数。例如:
b = Button(... command = button)
如果你想传递一个参数,比如“嘿”,你需要多写一点代码:
- 你可以创建一个中间函数,这个函数可以在没有参数的情况下被调用,然后再调用你的
button
函数, - 你可以使用
lambda
来创建一个叫做匿名函数的东西。它在各方面都像一个函数,只是没有名字。当你调用lambda
时,它会返回一个对这个创建的函数的引用,这意味着它可以用作按钮的command
选项的值。 - 你还可以使用 functools.partial
对我来说,lambda
是最简单的,因为它不需要像functools.partial
那样额外导入其他东西,尽管有些人觉得functools.partial
更容易理解。
要创建一个调用你的button
函数并传入参数的lambda函数,你可以这样做:
lambda: button('hey')
最终你得到的函数在功能上等同于:
def some_name():
return button('hey')
正如我之前所说,lambda
返回的是这个没有名字的函数的引用。由于引用是command
选项所期待的,所以你可以直接在创建按钮时使用lambda
:
b = Button(... command = lambda: button('hey'))
这个网站上有一个问题,里面有很多关于lambda的有趣评论。可以看看这个问题 为什么Python的lambda有用?。同样的讨论中还有 一个回答展示了如何在循环中使用lambda,当你需要将变量传递给回调时。
最后,可以查看 zone.effbot.org 上的文章,标题为 Tkinter回调,这是一个不错的教程。虽然对lambda
的介绍比较简略,但那里的信息可能仍然有用。