iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
Software Development

用 Python 打造最順手的良率統計工具:半導體工程師的模組化開發之道系列 第 17 篇

Day 17: 不用吃銀杏也有好記憶 - 使用 json 模組讓 GUI 程式能留存使用者資料

  • 分享至 

  • xImage
  •  

本篇目的:讀者可以了解如何管理系統設定與使用者設定,並且可學會使用 json 模組進行讀取與寫入

前言

本篇專欄的目的是讓製造業產線工程師的大家,可以做出符合自身需求資料整理輸出 GUI 程式,而我們作分析一定需要2種資料:產出資料與 SPEC。SPEC通常會由使用者在 UI 中進行輸入,而本篇專欄即將進入第四部分 ”使用者輸入資料的管理”,預計會提到以下內容:

  • 如何儲存常用參數與使用者設定資料 (day 17 ~ day 18)

  • 如何在使用者輸入與讀取 UI 資料時,做資料格式的驗證 (day 21~22)

  • 如何優化輸入介面,讓它有複製貼上、標示超規數據等方便功能 (day 19~20)

我們在 day 9~13 有提到如何讀取 UI 元件內的使用者輸入內容,但使用者們一定會希望程式能儲存他們的輸入,例如我們一定希望同樣的產品 SPEC 只要設定一次、就能產生多次結果,又或是給產線使用的程式一定是讓工程師設定並儲存、然後讓產線人員在執行時選參數。

今天 day 17 便會說明如何儲存這些使用者輸入資料,我們會選用 Python 內建的 json 模組處理這些事情,讓我們的 GUI 程式擁有長久的記憶,解放我們的工時。

Json 是什麼

Json 是一種廣泛使用的資料儲存格式,許多網路資料都適用此格式儲存,例如證交所的服務API、政府資料平台等。我們選用此格式儲存設定的原因如下:

  • 結構上與 dict 非常像,且 Python 有原生的 json 模組可處理

  • 檔案小

  • 人容易閱讀

Json 檔案打開來的格式如下。其中最外層的 {} 大括號包覆整個資料,資料內有屬性與值,資料之間用逗號隔開。資料內用雙引號 ”” 包覆起來的文字被稱為屬性,而屬性後面的對應值可以是字串、數字、陣列、字典、布林值、空值等。因此 json 可以作成巢狀結構,如下面的 data 內有多班公車資訊組成之 list。

Python 讀取時會將整個資料讀取成一個 dict。其中的屬性一定會被讀取成字串,而對應值的資料形式則由資料本身決定 (例如下面資料的 size,對應值是整數 0,但 "年度[民國年]" 的對應值會是字串而不是整數)。

{
  "contentType": null,
  "isImage": false,
  "size": 0,
  "data": [
    {
      "路線名稱": "綠幹線",
      "起迄站及行經重要站點": "臺南-新化-玉井",
      "路線類別": "幹支線",
      "業者": "興南",
      "年度[民國年]": "114",
      "統計": "1,075,948"
    },
    {
      "路線名稱": "綠1",
      "起迄站及行經重要站點": "新化-臺鐵南科-臺鐵善化",
      "路線類別": "幹支線",
      "業者": "興南",
      "年度[民國年]": "114",
      "統計": "44,498"
    },
  ]
}

設定檔的分類與儲存路徑

程式的設定檔大致上可分為儲存初始設定的設定檔,與在使用過程中儲存使用者輸入的設定檔。這兩種設定檔在存放位置、用途都有不同差別。以下會針對這兩個設定檔作介紹

系統初始設定檔

系統初始設定檔適合放不容易變動、或是在使用者第一次使用時載入的資料,例如下拉清單的選項、預設 SPEC 與路徑、讀取的欄位清單等。另外通常只會有一個 json 檔案,名稱常叫做 config.json。

我們通常會手動編寫 json 並編輯這些資料,儲存於與 main.py 相同的路徑中,方便之後打包成 exe 時可以直接將這個檔案一起包進去。

另外當我們執行專案打包的 exe 檔後,打包進去的外掛資源(如 config.json、圖示)在執行時會被解壓縮到系統的臨時目錄 sys._MEIPASS 中;而我們在開發時是用 cmd 執行,這些設定檔通常會自己放在與 main.py 相同的路徑。

為了讓程式在「開發期(執行 .py)」與「打包後(執行 .exe)」都能無縫找到檔案,我們可以編寫一個 get_resource_path 函式:

import sys
from pathlib import Path

def get_resource_path(relative_path: str | Path) -> Path:
    """
    取得資源檔案的絕對路徑
    相容於:
    1. 開發階段:直接執行 .py 檔 (以當前腳本所在目錄為準)
    2. 打包階段:PyInstaller 打包後的單一 exe 檔 (以 sys._MEIPASS 暫存目錄為準)
    """
    if hasattr(sys, '_MEIPASS'):
        # 打包成 exe 後,PyInstaller 解壓縮檔案的暫存資料夾
        base_path = Path(sys._MEIPASS)
    else:
        # 開發階段:當前 py 檔案所在的資料夾
        base_path = Path(__file__).resolve().parent

    return base_path / relative_path

# 使用範例:取得 config.json 的正確路徑
config_path = get_resource_path("config.json")

注意:此 function 必須放在 main.py 執行,否則開發階段取得的路徑會異常

使用者的設定

使用者的設定檔可以有很多個,例如輸出檔案格式的 SPEC 以及計算良率的 SPEC 可以分成兩個檔案,檔名為自己設定。

通常會設定使用者第一次開啟程式時,生成這些 json 檔案 (裡面是空的)。然後在使用者輸入資訊並按下存檔後寫入資訊到 json 檔案中。

在 Windows 系統中,這些使用者設定檔習慣儲存在 C:\Users\使用者名稱\AppData\Roaming\應用程式名稱 內,主要原因如下:

  • 具備完整讀寫權限:AppData 是使用者本機專屬目錄,一定可以寫入檔案

  • 升級軟體不遺失設定:未來就算重新發布 exe 檔,使用者的參數設定依然在存在該路徑中

在 Python 中,我們可以透過環境變數 APPDATA 取得該路徑,並搭配 mkdir 自動建立專屬資料夾:

import os
from pathlib import Path

# 專案名稱,也是程式本身的名稱
PROJECT_NAME = 'MyWaferTool'

# 透過 Windows 環境變數取得 AppData 路徑
app_data_dir = Path(os.environ["APPDATA"]) / PROJECT_NAME

# 自動建立資料夾 (parents=True 會遞迴建立上層資料夾,exist_ok=True 避免重複建立時報錯)
app_data_dir.mkdir(parents=True, exist_ok=True)

# 使用者設定檔完整路徑
user_setting_path = app_data_dir / "user_spec.json"

甚麼東西該放入 json 設定檔,或是該當作常數

我們另外設定一個 config.json 存放一些初始值的目的是避免 controller 等模組充滿了各種預設常數(例如多組讀檔/輸出路徑),造成編寫上的混亂。我們可以用以下方式判斷這個常數應該寫在 py 模組裡還是 config.json 中:

  • 應該寫在 py 模組裡:與程式執行流程、邏輯相關的項目,若有錯誤會導致 error 或是預期外的執行結果。例如前面提到的使用者 SPEC 設定檔的名稱、晶粒座標對應的 X/Y 欄位名稱、Qt 的 signal 、程式本身的名稱等

  • 應該寫在 config.json 裡:需要在程式開啟時載入,但會讓使用者選擇或修改、與程式執行流程較不相關的項目,例如下拉式選單的選項、初始的讀檔/輸出路徑、初始 SPEC 等

使用 json 模組讀寫 .json 檔案

大家應該會覺得前面蹲了這麼久的馬步,怎麼到這裡才說明 json 檔案如何處理。原因是因為 json 的模組使用上很簡單,跟讀寫 txt 非常類似,反而檔案路徑與存儲資料的的管理才是”使用者配置設定”的重點。

讀取 json

# file_path 是 json 的檔案路徑

if file_path.exists(): # 檢查檔案是否存在
    
  # 使用 open 開啟檔案,可以在讀取後自動關閉檔案
  # 讀取使用模式'r'
  # 編碼通常是 utf-8
    with open(file_path, 'r', encoding='utf-8') as f: 
        self.sys_config = json.load(f) # 讀取 json 檔案

將資料寫入 json

# 寫入使用模式'w'
# 寫入編碼一樣是 utf-8
# indent: 縮排數,讓檔案比較好閱讀,如上面的公車範例
with open(file_path, 'w', encoding='utf-8') as f:
    json.dump(data, f, ensure_ascii=False, indent=4)

To be continued →

今天我們了解了資料的分類、路徑的檢查方式、以及 json 檔案的讀寫。但如同 day 15 提到的,我們不該讓 json 的讀取寫入散佈在 controller 之中,而是應該用一個 class 去管理它。

我們在 day 14 中知道了 class 的建構方式,但那個 class 是關於 data pipline 的建構。而在明天 day 18 的文章中,我會說明如何實作一個 ”完整,可重複使用” 之功能模組,並且是從 0 開始定義架構。若我們能掌握定義架構的方法,無論之後是自己寫或是 vibe coding,都能產出符合自身期待的模組。


上一篇
Day 16: 千里姻緣一線牽- 用 signal 與 slot 為 UI 接上功能
下一篇
Day 18:萬丈高樓平地起 - 從零開始建立一個功能模組
系列文
用 Python 打造最順手的良率統計工具:半導體工程師的模組化開發之道 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言