2023年8月11日 星期五

Python 學習筆記 : 利用 eval() 從名稱字串取得物件實體

本周開始有時間改寫明中的字集測試專案, 這回是利用前陣子以 tkinter 撰寫工作上的三套軟體所累積的經驗, 將原本的命令列程式改寫成 GUI 軟體, 畢竟要文學院的人跑命令列太不人道. 

這回明中增添了八個檢驗規則, 使得總數來到 26 個 (規則 2~27), 因為除了正式運轉要執行這 26 個規則之檢測外, 我另外加寫了一個手動檢測介面, 既然有雙重用途, 所以我將每個規則都改寫成個別的函式, 其中全部檢測功能利用迴圈依序呼叫這 26 個函式, 依據其傳回值來判斷是要丟棄還是納入此字集. 

當然可以一個一個呼叫這 26 個函式, 這樣既直觀又簡單, 但我們都知道 simple is stupid, 而且缺乏匠氣, 所以我使用了 eval() 函式透過函式名稱字串來取得函式實體, 這樣程式就不會落落長, 而且顯得既匠氣又專業. 以下用簡化的範例來說明做法 :


測試 1 : 從函式名稱字串取得函式實體 

# 有特定命名規則之函式
def rule1():
    print('Running rule1.')

def rule2():
    print('Running rule2.')

def rule3():
    print('Running rule3.')

def rule4():
    print('Running rule4.')

# 用串列生程式動態產生函式名稱串列
function_names=[f'rule{i}' for i in range(1, 5)]
print(function_names)

# 用迴圈依序呼叫透過 eval() 從函式名稱字串取得之函式實體
for function_name in function_names:
    function=eval(function_name)  # 用 eval() 從函式名稱字串取得函式實體
    function()
   
此例的四個函式名稱都是有規則可循的, 所以可以用串列生成式產生函式名稱字串串列, 也可以很方便地用迴圈來逐一呼叫, 但呼叫前必須將函式名稱字串 function_name 傳給 eval(), 它會傳回該字串對應之函式實體, 這樣才能呼叫, 不能直接用字串變數 function_name 呼叫. 

執行結果如下 :

>>> %Run eval_function_1.py
['rule1', 'rule2', 'rule3', 'rule4']
Running rule1.
Running rule2.
Running rule3.
Running rule4.


eval() 的妙用還不只如此, 我們可以利用它從名稱字串取得任何物件, 例如 :


測試 2 : 從元件名稱字串取得 tkinter 物件實體

import tkinter as tk
from tkinter import ttk

def change_color():
    labels=[f'label_{i}' for i in range(1, 5)]
    print(labels)
    for idx, label in enumerate(labels):
        widget=eval(label)
        print(widget)
        widget.config(bg='yellow', text='Color changed!')

#建立視窗
root=tk.Tk()
root.title('eval()測試')
root.geometry('300x200')  # 設定視窗大小

label_1=tk.Label(root, text='Hello World 1')
label_1.pack()
label_2=tk.Label(root, text='Hello World 2')
label_2.pack()
label_3=tk.Label(root, text='Hello World 3')
label_3.pack()
label_4=tk.Label(root, text='Hello World 4')
label_4.pack()
button=ttk.Button(root, text='確定', command=change_color)
button.pack()

root.mainloop()

此例在視窗上放置了四個 Label 元件與一個按鈕, Label 物件的名稱是有規則的, 所以可以利用 eval() 從元件名稱字串取得元件之實體, 然後方便地使用迴圈來控制這些 Label 的內容, 此處是按下按鈕後更改 Label 的 text 與 bg 屬性, 結果如下 :



沒有留言 :