本篇目的:了解如何設計與組合 function,以模組化的方式建立可重複使用、易修改的腳本
在 Day 3~ Day 7,我們說明了很多分析 wafer 時常使用的情境與對應 code。而今天要來說明如何將這些功能以模組化程式設計的方式建構function,並且將這些 function 連接起來,串出我們所需要的功能。
若我們在處理 wafer 產出資料時,會先開啟好幾個 excel 檔案、刪除不使用的欄位、接著使用各種方式處理 data (例如公式、pivot table、圖表等),最後將整理完成的 data 存檔與放入報告中。我們這樣的每日任務其實就是資料科學中的 ETL(Extract, Transform, Load)。
ETL 的流程如下:
擷取 (Extract):擷取原始資料,資料一定要從來源中擷取出來才能分析。我們從 MES 系統上撈資料到 excel 的 ”開啟舊檔”,都是擷取資料的範疇。
轉換 (Transform):將原始資料轉化成有意義的分析資料,這會是花最多功夫的地方。我們手動處理 excel 表格,到使用腳本自動化處理資料清洗、篩選、分類,都屬於這個部分。
載入 (Load):將處理後的資料儲存起來。例如存下已處理好的 excel 資料等。
我們在 Day 3~ Day 7 討論的大量功能,不會脫離這 3 個分類,而在這裡我們要用模組化的方式將所需的功能串接起來。
好的資料處理腳本應該要像生產線的站點一樣,生產線的特點如下
職責分明:依功能區分成各種不同的站點
單一職責:一個站點只做該站點負責的事
低耦合度:新增或修改站點時,不會影響其他站點的運作
極大彈性:可以依照產品需求,自由設計需要的流程
可重複性:無論是 OP 或是 PE ,依設定操作機台時,都會得到相同的結果
而當我們在 Python 中定義函式、類別 (class) 時,就好像在 fab 中配置曝光機、蒸鍍機等各式功能不同的機器。這些函式、類別配置完成後,我們就可以在需要時使用或呼叫它。
機器是不會自己動的,需要先設計製程流程 (run card) 才能讓產線做出樣品。而要讓我們的 code 像貴司的生產線一樣正常運作 (理想上) 的話,我們也必須將各種不同物件像 run card 一樣依需求組合起來,這個過程又稱為工作流編排 (workflow orchestration)。
我們若將功能封裝起來並像串 run card 一樣設計工作流,會有以下優點
可在不同地方重複使用 (呼喚 Python 中最強大的咒語:import )
可以為函數取易讀性高的名字,讓整體工作流容易理解
容易增加功能,就像在 run card 中多加一站
好修改與除錯,可以只針對特定 function 做修改,且發生異常時也容易追蹤是哪個函式出問題

假設你要設計一組點單 function,客人可以選餐點編號,然後程式將編號轉換成餐點名稱並輸出。非常初學的人可能會這樣寫:
customer_order = (1,1,3,2,4) # 點餐編號
real_product = [] # 裝餐點名稱的 list
for sn in customer_order:
if sn == 1:
real_product.append('apple')
elif sn == 2:
real_product.append('banana')
elif sn == 3:
real_product.append('grape')
elif sn == 4:
real_product.append('pineapple')
上面的寫法是標準的腳本式寫法 (scripting)。雖然大家學生時寫 MATLAB 一定對這寫法不陌生,但以上面的腳本式寫法會有幾個問題
難以閱讀:無法第一眼就看出編號與餐點的對應
重複 code:當需要一次新增 10 個商品時,難道要多出 10 個 elif?
引用困難:我們很難在同一個 py 檔內重複使用此功能。通常這樣的寫法只能用複製貼上來重新使用,還要記得改 real_product 的名稱 (當然,if 迴圈裡的也要改喔)
無法對接 UI:這段 code 因為沒有封裝,因此也無法被其他指令呼叫。當未來想用排程工具或 UI 控制時,根本無法控制此段 code
若要將以上 code 改寫成模組化寫法,實踐方式如下。大致上可以將這套腳本分成以下3部分
常數的區塊:可以將一些不會讓 function 改動的值放在這,例如讀寫路徑、轉換常數等。獨立一個區塊的好處是容易閱讀與修改常數
Function 的區塊:建構各式功能的區塊,一個 function 最好只負責一個主功能,例如讀取檔案與清理檔案的 function 要分開。
執行的區塊:將 fucction 依照所需呼叫與串在一起的區塊,如同產品的 run card。這裡是定義整個腳本如何運行的地方。
# 常數的區塊
MENU = {1:'apple', 2:'banana', 3:'grape', 4:'pineapple',}
# Function 的區塊
def map_sn_to_product_name(order: Sequence[int], menu:dict[int,str]) -> list[str]:
product_list = []
for sn in order:
product = menu.get(sn)
if product is not None:
product_list.append(product)
else:
print(f'無此產品編碼: {sn}')
return product_list
# 執行的區塊
real_product = map_sn_to_product_name(customer_order, MENU)
並且可以觀察到以下的 code 比起原始資料有以下優點:
單一職責:這個 function 就只做"將編號與產品名稱對應”這件事,不會去管理產品名稱
極大彈性:新增產品只要在 MENU 內新增項目即可
容易閱讀:編號與產品的關係非常明確
可重複性:這個 function 可以重複被呼叫
Function 的設定方式請參考 Day 5
這段code 會演示以下功能
讀取資料並建立 dict[dataframe]
建立一 xlsx,內含各片的二維產出 (分頁)、以及各片的 Vf2 產出
輸出各片的 Vf2 mapping (透明背景 + bin 3 ~ 3.1),並且檔名為"片號_vf2.png"
並且這段 code 也可以按照上面分成三個區塊
程式碼連結:
在 Python 中,常數命名慣例為全大寫。我們可以將不會讓 function 改動的值放在這。
# 常數
OUTPUT = Path('E:/py_practice/sandbox/ithome_2026ironman')
READ_PATH = Path.cwd()/'test_data'/'normal'
HEADER_ROW_NUM = 7
READ_COL = ['SN', 'Vf1', 'Vf2', 'Vr1', 'Ir1', 'Po', 'Wd2', 'X', 'Y']
PO_BIN = [0,1000,1500,1700,1750,1800,1850,2000]
WD2_BIN = [0,442.5,445,447.5,450,452.5,455,457.5,500]
這裡就是建構功能的區塊。以下圖為例,我將讀取 data、衍生欄位計算、整理報表、繪圖等指令寫在這,並且讓一個 function 只對應到一個主要功能。

以下區塊便是由上到下,依序作業讀檔、清理 data、運算與輸出的地方。另外下面的 if __name__ == "__main__": 的用途是讓這個 py 檔只有在直接執行下 (不是被 import) 才會運行。
if __name__ == "__main__":
result_dict:dict[str, list[pd.DataFrame] | pd.DataFrame] = {}
# 讀檔與轉檔
dict_all_wafer = read_multi_wafer_data(READ_PATH, READ_COL, HEADER_ROW_NUM)
df_all_data = turn_mapdict_to_a_dataframe(dict_all_wafer)
# 將分 bin 結果放入 dataframe 中
df_all_data = yield_binning(df_all_data, 'Po',PO_BIN)
df_all_data = yield_binning(df_all_data, 'Wd2',WD2_BIN)
df_all_data['Count'] = 1
# 計算每片的各項產出
df_po_yield = cal_yield_by_wafer(df_all_data, col_name='Po_bin', data_type='percent')
df_wd_yield = cal_yield_by_wafer(df_all_data, col_name='Wd2_bin', data_type='percent')
result_dict['All_yield'] = [df_po_yield, df_wd_yield] # 放入輸出項目中
# 拆出逐片的產出並繪圖
for wafer_id, df_wafer in dict_all_wafer.items():
# 計算每片的 2D 產出
df_wafer['Count'] = 1
df_wafer = yield_binning(df_wafer, 'Po',PO_BIN)
df_wafer = yield_binning(df_wafer, 'Wd2',WD2_BIN)
df_a_wafer_2d = cal_2d_yield(df_wafer, col_name='Wd2_bin', row_name='Po_bin')
result_dict[wafer_id] = df_a_wafer_2d
# 繪製 mapping 圖
plot_wafer_mapping(wafer_id, df_wafer, 'Vf2',OUTPUT, display_mode='transparent', scale_mode='manual',manual_range=(3,3.1))
save_path = OUTPUT / 'result.xlsx'
to_excel_flexible(result_dict, save_path)
我們能自己建立所需的腳本了,但若要給其他人使用似乎還缺一個 UI。本專欄即將進入第二部分 “建構 UI 並與 Python 腳本搭配,我們會在 day 9 ~ day 13 學習如何用 Qt designer 做出 UI 介面。