Python学习笔记 - tkinter: 多个Button绑定类似函数的简单写法
在tkinter编程中,常会遇到这样的问题:多个Button绑定了一个类似的回调函数。例如,我们要编写一个做选择题的程序,设置了4个按钮,点击各个按钮,就分别给选择答案列表加上一个A, B, C, D。使用传统代码:
import tkinter # 导入tkinter模块
items = [] # 初始化选项列表
root = tkinter.Tk() # 创建窗口
root.title('普通代码程序') # 设置标题
root.geometry('500x300') # 设置窗口大小为500x300
root.resizable(width=False, height=False) # 禁止修改窗口大小
def choose_a():
items.append('A')
def choose_b():
items.append('B')
def choose_c():
items.append('C')
def choose_d():
items.append('D')
tkinter.Button(root, text='A选项', command=choose_a).pack() # root参数的完整形式是master=root
tkinter.Button(root, text='B选项', command=choose_b).pack()
tkinter.Button(root, text='C选项', command=choose_c).pack()
tkinter.Button(root, text='D选项', command=choose_d).pack() # 更多选项以此类推
def show():
'''输出已选择的选项列表'''
print(items)
tkinter.Button(master=root, text='输出已选选项', command=show).pack()
root.mainloop() # 窗口持久化
在上述代码中,4个choose函数过于麻烦。而且,当选项数量不确定时,这种方法即不可行。因此,使用lambda关键字。
lambda关键字可以声明一个匿名函数。例如,在按钮:
def func():
print('Hello, world!')
tkinter.Button(master, command=func) # 注意master要填上父容器的实例
等价于下列代码:
def func(text: str):
print(text)
tkinter Button(master, command=lambda: func(text='Hello, world!'))
因此, 最开始的选择题代码, 可以改为:
import tkinter # 导入tkinter模块
items = [] # 初始化选项列表
root = tkinter.Tk() # 创建窗口
root.title('lambda代码程序1') # 设置标题root.geometry('500x300') # 设置窗口大小为500x300root.resizable(width=False, height=False) # 禁止修改窗口大小
def choose(item):
items.append(item)
tkinter.Button(root, text='A选项', command=lambda: choose('A')).pack() # 选项按钮
tkinter.Button(root, text='B选项', command=lambda: choose('B')).pack()
tkinter.Button(root, text='C选项', command=lambda: choose('C')).pack()
tkinter.Button(root, text='D选项', command=lambda: choose('D')).pack()
def show():
'''输出已选择的选项列表'''
print(items)
tkinter.Button(master=root, text='输出已选选项', command=show).pack()
root.mainloop() # 窗口持久化
当然,因为choose
函数只有一行代码构成,因此可以写成:
import tkinter # 导入tkinter模块
items = [] # 初始化选项列表
root = tkinter.Tk()
root.title('lambda代码程序2') # 设置标题root.geometry('500x300') # 设置窗口大小为500x300
root.resizable(width=False, height=False) # 禁止修改窗口大小
tkinter.Button(root, text='A选项', command=lambda: items.append('A')).pack()
tkinter.Button(root, text='B选项', command=lambda: items.append('B')).pack()
tkinter.Button(root, text='C选项', command=lambda: items.append('C')).pack()
tkinter.Button(root, text='D选项', command=lambda: items.append('D')).pack()
def show():
print(items)
tkinter.Button(root, text='输出已选选项', command=show).pack()
root.mainloop() # 窗口持久化
用for
循环改进tkinter.Button
的定义:
import tkinter # 导入tkinter模块
items = [] # 初始化选项列表root = tkinter.Tk() # 创建窗口
root = tkinter.Tk()
root.title('lambda+for代码程序') # 设置标题root.geometry('500x300') # 设置窗口大小为500x300
root.resizable(width=False, height=False) # 禁止修改窗口大小
for item in ('A', 'B', 'C', 'D'): # 循环定义
tkinter.Button(root, text=item, command=lambda: items.append(item)).pack()
def show() -> None: # 顺便说一句, -> None表示返回None. 也可以是bool, tuple, tkinter.Misc等等
print(items)
tkinter.Button(root, text='输出已选选项', command=show).pack()
root.mainloop() # 窗口持久化
总之, 可以写一个含有共同内容的function, 把不同内容作为参数,再用lambda
关键字改写。
注:感谢**@xiansr**同志的提醒,之前的lambda
写法确实不可用,目前已经修改,谢谢。
其实,在其他时候也可以使用lambda
。这里仍然使用tkinter
作为示例:
import tkinter # 导入tkinter模块
import tkinter.messagebox as msg # 导入消息框模块
root = tkinter.Tk() # 定义一个窗口
root.title('lambda示例窗口')
root.geometry('500x300')
root.resizable(width=False, height=False)
def show(event: tkinter.Event, key: str) -> None: # event是tkinter.Misc中bind传入参数所留的参数位置,不留位置会报错
msg.showinfo(title='单击了按钮', message='单击了%s按钮' % key) # 也可改为'单击了{}按钮'.format(key)
root.bind('<KeyPress-A>', lambda: show(key='A')) # 绑定了A按钮事件,当A键被按下时执行
root.bind('<KeyPress-B>', lambda: show(key='B'))
root.bind('<KeyPress-C>', lambda: show(key='C'))
root.mainloop() # 窗口持久化
当然,如果你对Python已经比较熟悉,可以用类来实现。
关于类中__call__
方法的说明:
class Class:
# Python2.x必须写class Class(object):
def __init__(self, func, *args, **kwargs):
# 函数
self.func = func
# 参数
self.args = args
# 键值对形式的参数
self.kwargs = kwargs
def __call__(self):
'''
PyCharm可能默认为
def __call__(self, *args, **kwargs):
把形参的*args和**kwargs去掉即可
'''
self.func(self.args, self.kwargs)
# return self.func(self.args, self.kwargs)
生成实例时,会自动调用__init__
方法,这时把参数func, args, kwargs传入,当实例被调用时,执行__call__
方法,请看下面的例子:
In[01]: # 定义类
class Homework:
def __new__(self):
# 只是演示需要,实际不需要自定义此方法。
# 生成实例时自动形成,次序在__init__之前。
super().__new__()
print('已拿到作业表单,准备布置作业')
def __init__(self, subject, page):
# 科目
self.sub = subject
# 练习册页数
self.page = page
print('已布置作业')
def __call__(self, data):
print('提交{}作业,练习册第{}页,提交时间{}')
# 有无返回值均可
return True
In[02]: example = Homework('数学', 31)
已拿到作业表单,准备布置作业
已布置作业
In[03]: call = example('10月29日')
提交数学作业,练习册第31页,提交时间10月29日
In[04]: call
Out[03]: True
由此可见,当示例被调用时(即Class('__init__参数')('__call__参数')
),会执行类的__call__
方法,返回值即__call__
方法的返回值(默认为None
)。
注:当没有__call__
方法时,如果实例被调用,将会抛出异常,提示Uncallable
。
因此,上文中的show函数,可以改为:
class Show:
def __call__(self, event: tkinter.Event)
msg.showinfo(title='单击了按钮', message='单击了%s按钮' % self.key) # 也可改为'单击了{}按钮'.format(key)
def __init__(self, key: str):
self.key: str = key
bind中的lambda
语句可改为:
root.bind('<KeyPress-A>', Show('A'))
root.bind('<KeyPress-B>', Show('B'))
root.bind('<KeyPress-C>', Show('C'))
调用实例的实际原理是:
Show('C')(event) # event由root.bind自动传入
编者技术有限,如有不足敬请指教!