为什么我的按钮命令在创建时立即执行,而不是在点击时?

106 投票
5 回答
42178 浏览
提问于 2025-04-16 16:18

我的代码是:

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 个回答

15

示例图形界面:

假设我有这样一个图形界面:

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

镜像

16

你需要创建一个没有参数的函数,这样你就可以把它当作命令来使用:

b = Button(admin, text='as', command=lambda: button('hey'))

可以查看这份文档中的“传递参数给回调”部分。

125

考虑一下这段代码:

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的介绍比较简略,但那里的信息可能仍然有用。

撰写回答