GUI 小程式
到目前為止的程式都在文字介面(終端機)裡跑。這個專題改用 Python 內建的 tkinter,做一個有視窗、文字標籤和好幾顆按鈕的圖形介面(GUI)程式:按下哪顆按鈕,標籤就顯示哪顆被按了。
把它存成 gui_app.py,在終端機執行 python gui_app.py,或用 VS Code 的執行鈕,就會跳出視窗:
import functoolsimport tkinter as tk
def update_label_text(text): label_message.config(text=text)
window = tk.Tk()window.title("我的 GUI 程式")
label_message = tk.Label(window, text="你好!")label_message.pack()
for button_name in ["按鈕一", "按鈕二", "OKOK", "NONO"]: button_command = functools.partial( update_label_text, f"{button_name}被按了", ) button = tk.Button( window, text=button_name, command=button_command, ) button.pack()
window.mainloop()import tkinter as tk:載入 tkinter,慣例上簡稱tk。tk.Tk():建立主視窗;window.title(...)設定視窗標題。tk.Label(window, text="你好!"):一個顯示文字的標籤,放在window裡。label_message.config(text=...)可以事後改掉它顯示的內容,update_label_text這個函式做的就是這件事。.pack():把元件擺進視窗,tkinter 會自動由上往下排。tk.Button(window, text=..., command=...):一顆按鈕。command要放一個函式,按下去 tkinter 就會呼叫它。window.mainloop():讓視窗開著、等你操作。這行一定要放在最後。
為什麼 command 不能直接寫 update_label_text("按鈕一被按了")
Section titled “為什麼 command 不能直接寫 update_label_text("按鈕一被按了")”command 要的是一個「按下去才執行」的函式,所以只能把函式本身交給它,不能加括號。可是我們又想讓每顆按鈕顯示不同的文字,也就是希望 tkinter 按下去時幫我們呼叫 update_label_text("按鈕一被按了")。
如果寫成 command=update_label_text("按鈕一被按了"),括號一加,這行程式當場就會執行 update_label_text,標籤立刻變成「按鈕一被按了」,然後把它的回傳值 None 交給 command。按鈕真的被按下時,反而什麼都不會發生。
functools.partial 就是解決這個問題的工具:它把函式和要帶的引數先「綁」在一起,做出一個新的、不用再給引數的函式,等到真的被呼叫時才執行。下面這一節會單獨講它。
先一顆一顆寫
Section titled “先一顆一顆寫”不用迴圈的話,兩顆按鈕可以這樣寫,每顆都各綁一段文字:
button_1 = tk.Button( window, text="按鈕一", command=functools.partial(update_label_text, "按鈕一被按了"),)button_1.pack()
button_2 = tk.Button( window, text="按鈕二", command=functools.partial(update_label_text, "按鈕二被按了"),)button_2.pack()再改成用迴圈
Section titled “再改成用迴圈”按鈕一多,一顆一顆寫就太囉唆了。完整程式裡改成把按鈕的文字放進串列,用 for 走過去,每一圈做一顆按鈕:
functools.partial(update_label_text, f"{button_name}被按了")先做出這顆按鈕專用的函式。tk.Button(window, text=button_name, command=button_command)用它建立按鈕。button.pack()擺進視窗。
四顆按鈕各自記住了自己那段文字,因為 partial 在每一圈都用當時的 button_name 綁了一個新的函式。想再加按鈕,只要往串列多塞一個名字。
補充:functools.partial
Section titled “補充:functools.partial”functools.partial(函式, 引數...) 會回傳一個新的函式,它記住了你先給的那些引數;之後呼叫這個新函式時,這些引數會自動補上,再接你當下給的其他引數。用一個純計算的例子最清楚:
import functools
def add(n1, n2): return n1 + n2
# def add_1_and_2():# return add(1, 2)add_1_and_2 = functools.partial(add, 1, 2)
# def add_1_with(n2):# return add(1, n2)add_1_with = functools.partial(add, 1)
print(add_1_and_2())print(add_1_with(2))print(add_1_with(3))add_1_and_2 = functools.partial(add, 1, 2):兩個引數都先給了,add_1_and_2()不用再帶任何東西,效果就等於註解裡那個手寫的函式。add_1_with = functools.partial(add, 1):只先給了n1,剩下的n2留到呼叫時再給,所以add_1_with(2)是add(1, 2),add_1_with(3)是add(1, 3)。
按「下一步」慢慢走,注意 functools.partial(...) 那幾行沒有真的去呼叫 add,要到最後三個 print 才會進到 add 裡面。GUI 程式要的就是這樣:先把「要做什麼、帶什麼引數」準備好,等按鈕被按下再執行。
v0.9.1 (c924b5b)