本篇目的:讀者可以從零開始,以”目標設定→流程設計→定義手法”的方式,做出一個擁有完整功能、易修改的模組。
在 day 14 中我們知道如何以 class 的方式管理 data pipline,但 class 還有另一個使用方式:建立獨立的功能模組。例如前面 day 3 ~ day 8 一直在使用的 pd.Dataframe 就是一個為了整理表格資料所設計出來的 class,這個 class 當然也會實作出 sort_values 、 pivot_table 等表格相關的 method。而我們今天要做的便是這樣的應對特定情境的 class。
在現在 2026 年,絕大部分的模組使用方法都可以在 Google 或各種生成式 AI 中的對話中取得。因此比起知道模組的各種功能如何叫出來,我們更應該知道我們要做什麼、然後需要設計出甚麼功能。模組設計就像準備工具箱一樣:知道工具箱的應用情境、準備對應的工具、並且在需要時將工具箱內的工具拿出來用。
因此在今天的文章中,我們就要來打造自己的工具箱。我們會以 day 17 的設定檔模組開始,以我們平常做專案規劃的方式,從”目標回推到手法”來設計模組。並且示範以心智圖、class diagram 等方式繪製出結構。藉由此流程不僅可以建立**「程式架構」**的概念,還能將這些圖表交給 AI 生成模組出來。
在實際編寫 class 前,一定要先知道”這個 class 預計要有甚麼功能,我應該做些甚麼才能達成這些功能”。以完成一個 json 管理 class 為例,它應該要能夠管理 day 17 提到的系統設定檔、使用者設定檔,也要設計讓使用者容易讀取 SPEC 的方式。藉由從大目標到實際手法的設計,我們可以知道這個 class 要實際做出哪些 method。
我們知道了目的,就可以先來想一個好用的 class 需要有哪些功能並將其列出來。以這個 class 來說,我希望有以下功能:
User 在實際應用情境不同下 (只有使用者設定檔/ 只有系統設定檔 / 都有),都可以用此 class 管理設定檔資料
User 在實例化這個 class 後,可以簡單的讀取對應設定檔資料
可以指定設定檔內容作新增/刪除等修改
對應上面的功能,會有以下的細節
Class 內部應該要針對 "沒有對應設定檔 ” 的情況下自動輸出空 dict
讀取設定檔資訊的方式跟 dict 類似
要設計修改設定檔內容的方法,須同步更新物件內資訊與 json 檔
下面的 mindmap 便示範了這樣的流程。中間的雲是這個 class 的目的,雲周圍的節點是這個 class 要有的功能。我們一路從目的->功能繼續寫下去,最後可以看到尾端就是我們要實作之 method。

確認了應該要實作的 method 後,我們可以規劃這個 class 的實際架構,例如 property 等。以下使用了 class diagram 來表示這個 class 的組成,上面用粗體標示的是 class 的名稱、中間的格子是該 class 會有的 property,下面的格子是這個 class 會設定的方法。

這些圖表使用 Mermaid 繪製,大家可以到這個網站試試看。使用 Mermaid 可以讓生成式 AI 更加了解我們需要的程式結構
藉由上面的思考方式與圖表輔助,我們 (又或是接下來要開始幹活的生成式 AI) 對於 class 功能與所需架構應有清楚的認知。以下的示範例便是根據上面的圖表實作出 method。
Class 結構定義請見 day 14,今日文章主要說明 class 架構的思考流程
我們的目的是 class 實例化成物件後,user 就可以直接讀取裡面存下來的設定檔資訊,因此我們會在 __init__ 內設定 ”初始化使用者設定資料夾 → 初始化使用者設定檔案 → 讀取 json 資料到記憶體” 的流程。
而我們在 mindmap 中有提到 “若 user 有傳入使用者設定檔名稱,就要保證這些檔案存在“ (也就是檢查與初始化),因此若 user_config_name 有傳入,就會執行 ”初始化使用者設定資料夾 → 初始化使用者設定檔案” 這個流程。
class JsonManager:
def __init__(self, project_name: str, user_config_name: list[str] = None):
# 依據 Class Diagram 定義私有屬性
self._project_name = project_name
self._user_config_name = user_config_name if user_config_name else []
# 公開的記憶體資料緩衝區
self.sys_config: dict | None = None
self.user_config: dict[str, dict[str,list[dict[str, Any]]]] = {}
# 1. 初始化使用者設定檔與資料夾
if self._user_config_name:
self._init_user_config(self._user_config_name)
# 2. 自動載入設定檔至記憶體
self._load_sys_config()
self._load_user_config()
def _get_app_data_dir(self) -> Path:
"""取得使用者設定資料夾路徑 (內部使用,使用 _project_name)"""
return Path(os.environ.get("APPDATA", ".")) / self._project_name
def _init_user_config(self, name_list: list[str]):
"""檢查並初始化使用者設定資料夾與各個 json 檔案"""
app_data_dir = self._get_app_data_dir()
# 步驟一:檢查資料夾是否存在,不存在則遞迴建立
if not app_data_dir.exists():
app_data_dir.mkdir(parents=True)
print(f"已建立設定檔資料夾:{app_data_dir}")
# 步驟二:檢查清單內的 json 檔案,不存在則寫入空字典 ({})
for name in name_list:
file_path = app_data_dir / f"{name}.json"
if not file_path.exists():
with open(file_path, "w", encoding="utf-8") as f:
json.dump({}, f, ensure_ascii=False, indent=4)
print(f"已建立預設設定檔:{file_path.name}")
初始化使用者路徑與檔案後,就會開始讀取 json 檔案內的內容。為了使用方便, 在 __init__ 中就會直接呼叫讀取 method,實例化後就會直接讀檔。
針對不同設定檔,我們設計以下兩種存取方式:
系統設定檔:user 直接讀取物件 property 即可 ( json_manager.sys_config )
使用者設定檔:定義一個 get_user_spec method,讓 user 可以傳入 SPEC 名稱後讀取所需資料。
get_user_spec 中不直接使用 get 方法、並且另外加上檢查的原因是讓 user 可以第一時間發現錯誤。避免 user 打錯名稱後”正常”回傳空 dict,後續使用者就算發現資料有問題,也很難發現自己打錯名稱。
這種設計方式稱為 Fail-Fast (快速失敗),錯誤會在發生時就發出通知而不是勉強執行。就像應該要在製程各站的檢查發現製程的異常,而不是到最後檢測時才發現
def _get_resource_path(self, relative_path: str) -> Path:
"""取得系統設定檔的絕對路徑 (相容開發環境與 PyInstaller 打包後的 exe)"""
if hasattr(sys, "_MEIPASS"):
base_path = Path(sys._MEIPASS)
else:
base_path = Path(__file__).resolve().parent
return base_path / relative_path
def _load_sys_config(self):
"""讀取系統設定檔並寫入 sys_config 屬性 (固定讀取 config.json)"""
file_path = self._get_resource_path("config.json")
if file_path.exists():
with open(file_path, "r", encoding="utf-8") as f:
self.sys_config = json.load(f)
else:
self.sys_config = {}
def _load_user_config(self):
"""讀取所有指定的使用者設定檔並寫入 user_config 屬性"""
if not self._user_config_name:
return
app_data_dir = self._get_app_data_dir()
for name in self._user_config_name:
file_path = app_data_dir / f"{name}.json"
if file_path.exists():
with open(file_path, "r", encoding="utf-8") as f:
self.user_config[name] = json.load(f)
def get_user_spec(self, spec_name: str) -> dict:
"""使用者可以傳入名稱後得到對應資料,沒有設定檔的話會跳出 error"""
if spec_name in self._user_config_name:
return self.user_config.get(spec_name)
else:
raise ValueError(f'無此SPEC設定檔: {spec_name}')
使用者設定可能會在使用過程中修改 (例如新增一個產品的 SPEC),因此我們也要做出一個寫入 SPEC 到物件以及檔案的方法。以下方法 save_user_config 不僅會更新記憶體中的 user_config ,還會透過 json.dump 將資料檔案寫入到 json 檔案中。
實際上使用時我們可以參照 day 16 的連接方式,在 controller 中連接此 method 與UI 元件。當 user 在 UI 上點擊「儲存設定」按鈕時,我們會將資料打包並傳入 save_user_config。
def save_user_config(self,spec_name: str ,new_item_name: str, data: dict):
"""
將新資料更新到 class property 與指定的設定檔內
"""
# 防呆檢查:若傳入未定義的設定檔名稱則及早報錯
if spec_name not in self._user_config_name:
raise ValueError(f"無此 SPEC 設定檔:{spec_name}")
# 擷取出要編輯的 spec 並寫入
new_data = self.user_config[spec_name]
new_data[new_item_name] = data
# 更新資料到 class property 內
self.user_config[spec_name] = new_data
# 寫入實體檔案:將該 spec 完整的內容寫入對應的 json 檔案
file_path = self._get_app_data_dir() / f"{spec_name}.json"
with open(file_path, "w", encoding="utf-8") as f:
json.dump(new_data, f, ensure_ascii=False, indent=4)
print(f"已成功更新並儲存設定至:{file_path.name}")
以下示範如何應用這個 class。首先我們先回到 controller 並在 __init__ 內並實例化 JsonManager ,我們就可以讀取 self.json_manager 內的資料取得設定,並且在 中指派 UI 的修改與新資訊的寫入。
這裡展現了核心架構的關注點分離:UI 只管呈現、JsonManager 只管設定檔的讀寫與本機路徑,而 controller 負責調度。
# 示範在 Main Controller 中整合 JsonManager 與 UI
from PySide6.QtWidgets import QMainWindow
# from ui_main import Ui_MainWindow # Qt Designer 轉換出來的 UI 類別
# from config_manager import JsonManager # 剛剛完成的 JsonManager 模組
class MainController(QMainWindow):
def __init__(self):
super().__init__()
# self.ui = Ui_MainWindow()
# self.ui.setupUi(self)
# 建立 JsonManager 實例
self.json_manager = JsonManager(
project_name="MyWaferTool",
user_config_name=["user_spec", "last_status"]
)
# 讀取系統設定 (config.json) 並填入下拉選單
sys_settings = self.json_manager.system_settings
if "machine_list" in sys_settings:
self.ui.combo_machine.addItems(sys_settings["machine_list"])
# 連接按鍵到設定檔寫入功能
self.ui.a_button.clicked.connect(self.add_data_to_user_spec)
def add_data_to_user_spec(self):
"""將UI上的SPEC輸入寫入到設定檔中"""
# 讀取相關設定
new_item_name = self.ui.a_lineedit.text()
# data = self...... (讀取使用者輸入)
# 寫入到管理器之中
self.json_manager.save_user_config("user_spec", new_item_name, data)
我們今天學會了 class 設計的方式,並且實做了一個設定檔管理模組。
在明天 day 19 的教學中,我們會用與今天相同的 class 設計方式,來實作 day 11 沒有說明的 QTableWidget 讀寫方式。在這個 class 中,我們會將 QTableWidget 會用到的功能,例如讀取資料並轉成適當格式、增加表格的欄/列、寫入資料到表格中,都整合到單一 class 中。