🚀 Day 4:打造「具備持久化與完整 CRUD 功能的自動存檔記帳系統」

5 天全日營隊 (Day 4 / 總時數 6 Hours) | 解鎖硬碟永久記憶、CRUD 大滿貫與報表匯出

📖 為什麼學 these?——「拯救金魚腦!硬碟黑盒子」關卡冒險故事線

💡 故事情境:理財咖啡館升級!安裝關機資料不歸零的硬碟筆記本

前三天我們的程式資料都暫存在記憶體(RAM)裡,只要關掉 VS Code 重新開機,剛記下的帳務就全消失了。今天我們要幫理財咖啡館安裝 **硬碟黑盒子(JSON 檔案)與災難復原機制**:

單元名稱 記帳程式關卡角色 為什麼這個單元不可或缺?(技術關聯)
單元 1:持久化概念與 JSON 💾 硬碟黑盒子與通用文字檔 RAM 有金魚腦失憶症!學習將 Python 的 List/Dict 資料序列化(Serialization)轉換為跨語言通用的 JSON 文字格式。
單元 2:檔案讀寫 (with open) 📖 自動關檔的保險箱筆記本 使用 with open 安全讀寫檔案,不用擔心忘記關檔(f.close)造成資料遺失,並加上 UTF-8 防亂碼!
單元 3:開機自動讀檔與防呆 🛰️ 無縫開機探針檢測 首次開機時 expenses.json 還不存在!透過 os.path.exists() 先做探針檢測,預防開機直接當機。
單元 4:CRUD 刪除與修改實作 🛠️ 完整增刪查改大滿貫 (CRUD) 記錯金額怎麼辦?實作 Complete CRUD(新增 C、讀取 R、修改 U、刪除 D),掌握現代軟體最核心的四大操作。
單元 5:資料備份與報表匯出 📄 老闆最愛的財務月報表 (.txt) 將 JSON 資料加工導出一份排版整齊的純文字財務月報表(.txt),體驗商業軟體匯出報表的功能!
單元 6:破壞性測試與容錯修復 🚑 黑盒子災難復原機制 (os.rename) JSON 檔被誤刪大括號損毀了!撰寫容錯(Fault Tolerance)復原程式,將壞檔備份改名並自動重置系統。

📌 課程前導與簡介說明

🎓 教師心法與教學策略 (Teacher Only)
  • 營隊定位:達成**「資料持久化(Data Persistence)」**,讓程式關機後資料依然完好如初。
  • 高中生心理特性:體驗關閉 Terminal 重開資料還在的震撼,完成 Complete CRUD 實作。
  • Django 銜接點:JSON 讀寫對應 Django ORM 的 .save().delete(),開機自動讀檔對應資料庫連線與 Serialization。
🌟 學生自主學習指引 (Student Self-Study)

歡迎來到 Day 4!今天我們要把記帳資料永久保存在硬碟裡!請閱讀每個單元的 **故事單元說明**,點擊 **延伸閱讀** 展開深層觀念與 Google 搜尋,並善用每區塊的 **AI 協作除錯提示詞**!

💻 Day 4 專案總成果:具備持久化與完整 CRUD 功能的自動存檔記帳系統

【教師參考解答】學生於 Day 4 結束時將能獨立撰寫的完整程式:

"""
 Day 4 最終成果:具備持久化與完整 CRUD 功能的自動存檔記帳系統
 核心概念:JSON 檔案序列化/反序列化 -> 開機自動讀檔 -> CRUD 操作 -> 報表匯出
"""
import json
import os
import datetime

DATA_FILE = "expenses.json"

# ==========================================
# 💾 資料持久化模組 (對應 DB Access & ORM Persistence)
# ==========================================

def load_data():
    """ 開機自動讀檔 (模擬 DB Connection & QuerySet Fetch) """
    if not os.path.exists(DATA_FILE):
        return []
    try:
        with open(DATA_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    except (json.JSONDecodeError, Exception) as e:
        print(f"⚠️ 資料檔讀取異常或格式損毀 ({e}),已啟動預防性備份並重置清單。")
        return []

def save_data(records):
    """ 寫入 JSON 檔案 (模擬 DB .save() 寫入與 Commit) """
    with open(DATA_FILE, "w", encoding="utf-8") as f:
        json.dump(records, f, ensure_ascii=False, indent=4)

# ==========================================
# 🚀 系統主程式與 CRUD 路由
# ==========================================

records = load_data()
print(f"📂 開機成功!已自動從 [{DATA_FILE}] 載入 {len(records)} 筆歷史紀錄。")

while True:
    print("\n" + "=" * 45)
    print("      💾 持久化 CRUD 終端機記帳系統 💾      ")
    print("=" * 45)
    print("  [1] ➕ 新增消費紀錄 (Create)")
    print("  [2] 📊 查看所有明細 (Read)")
    print("  [3] ✏️ 修改紀錄金額 (Update)")
    print("  [4] ❌ 刪除特定紀錄 (Delete)")
    print("  [5] 📑 匯出財務月報表 (.txt)")
    print("  [6] 🚪 儲存並離開系統")
    print("=" * 45)

    choice = input("👉 請選擇功能選項 (1-6): ").strip()

    if choice == "1":
        # --- [C]reate 新增 ---
        item = input("👉 請輸入品項名稱: ").strip()
        if not item:
            print("⚠️ 名稱不能為空!")
            continue
        try:
            price = int(input("👉 請輸入金額: "))
        except ValueError:
            print("❌ 錯誤:金額必須是純數字!")
            continue
        
        cat = input("👉 請輸入分類 (預設: 一般): ").strip() or "一般"
        today = datetime.date.today().strftime("%Y-%m-%d")

        records.append({"name": item, "price": price, "category": cat, "date": today})
        save_data(records)
        print(f"✅ 已新增並自動存檔:{item} ${price} 元 [{cat}]")

    elif choice == "2":
        # --- [R]ead 讀取列表 ---
        if not records:
            print("📭 目前尚無任何記帳紀錄!")
            continue
        print("\n--- 📊 完整記帳明細列表 ---")
        total = 0
        for idx, r in enumerate(records, start=1):
            print(f"[{idx}] {r['date']} | {r['name']:<10} | ${r['price']:<5} | [{r['category']}]")
            total += r["price"]
        print("-" * 45)
        print(f"💵 總計累計金額:${total} 元")

    elif choice == "3":
        # --- [U]pdate 修改 ---
        if not records:
            print("📭 沒有可修改的資料!")
            continue
        print("\n--- ✏️ 修改紀錄金額 ---")
        for idx, r in enumerate(records, start=1):
            print(f"[{idx}] {r['name']} - 當前金額: ${r['price']}")
        try:
            target_idx = int(input("👉 請輸入要修改的編號: ")) - 1
            if 0 <= target_idx < len(records):
                new_price = int(input(f"👉 請輸入 [{records[target_idx]['name']}] 的新金額: "))
                records[target_idx]["price"] = new_price
                save_data(records)
                print("✅ 修改完成並已更新檔案!")
            else:
                print("⚠️ 輸入編號超出範圍!")
        except ValueError:
            print("❌ 錯誤:請輸入有效的數字編號與金額!")

    elif choice == "4":
        # --- [D]elete 刪除 ---
        if not records:
            print("📭 沒有可刪除的資料!")
            continue
        print("\n--- ❌ 刪除特定紀錄 ---")
        for idx, r in enumerate(records, start=1):
            print(f"[{idx}] {r['name']} - ${r['price']} ({r['category']})")
        try:
            target_idx = int(input("👉 請輸入要刪除的編號: ")) - 1
            if 0 <= target_idx < len(records):
                deleted = records.pop(target_idx)
                save_data(records)
                print(f"🗑️ 已成功刪除:{deleted['name']} ${deleted['price']} 元")
            else:
                print("⚠️ 輸入編號超出範圍!")
        except ValueError:
            print("❌ 錯誤:請輸入有效的數字編號!")

    elif choice == "5":
        # --- 匯出文字報表 ---
        report_file = "monthly_report.txt"
        with open(report_file, "w", encoding="utf-8") as f:
            f.write("=== 📊 個人財務月報表 ===\n")
            f.write(f"匯出日期: {datetime.date.today()}\n")
            f.write("-----------------------------------\n")
            tot = 0
            for r in records:
                f.write(f"• {r['date']} | {r['name']:<10} | ${r['price']}\n")
                tot += r["price"]
            f.write("-----------------------------------\n")
            f.write(f"總累計花費: ${tot} 元\n")
        print(f"📄 成功匯出財務報表至檔名:[{report_file}]")

    elif choice == "6":
        save_data(records)
        print("\n💾 系統資料已保存,期待下次運算!再見!👋")
        break

    else:
        print("⚠️ 無效的選項,請輸入數字 1 至 6!")

【學生自主實作框架】請根據今日各單元所學,將空缺的 `____` 填入正確程式碼:

"""
 🎯 Day 4 自主挑戰:請將下方的 ____ 填入正確的 JSON、with open 與 pop 語法!
"""
import json
import os

DATA_FILE = "expenses.json"

# 1. 寫檔存檔函式
def save_data(records):
    # 提示:開啟檔案寫入 mode='w' 並指定 utf-8 編碼
    with open(DATA_FILE, "____", encoding="utf-8") as f:
        # 提示:將物件寫入檔案的 json 函式
        json.____(records, f, ensure_ascii=False, indent=4)

# 2. 開機讀檔防呆
def load_data():
    if not os.path.____(DATA_FILE): # 提示:檢查檔案是否存在的函數
        return []
    with open(DATA_FILE, "r", encoding="utf-8") as f:
        return json.____(f) # 提示:從檔案載入 json 物件的函數

records = load_data()

# 3. 刪除資料 (Delete)
target_idx = int(input("輸入要刪除的編號 (1-based): ")) - 1
if 0 <= target_idx < len(records):
    deleted = records.____(target_idx) # 提示:清單移除指定索引項目的方法
    save_data(records)
    print("✅ 已刪除並自動存檔!")

⏰ 單元 1:持久化概念與 JSON 解析(09:00 - 10:00)

💾 故事單元說明:硬碟黑盒子與通用文字檔

電腦的記憶體(RAM)像是「快閃記憶卡」,速度極快但一斷電資料就全消失了!而硬碟(Disk)則是「金鋼不壞保險箱」。為了把 Python 的清單與字典存進硬碟,我們需要將它們轉換成跨語言通用的文字格式 **JSON (JavaScript Object Notation)**。這個過程稱為 **「序列化 (Serialization)」**!

💡 深度觀念:什麼是序列化 (Serialization) 與 JSON?

將記憶體中的物件結構轉化為可儲存或傳輸的位元組/字串過程稱為序列化:

  • json.dumps()json.loads():前者將 Python 物件轉為 JSON 字串;後者將 JSON 字串解析還原。
  • 關鍵參數 ensure_ascii=False:防止中文字元被強制轉換為萬國碼轉義字串 (如 `\u73cd\u4e76`)。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請向高中生說明 RAM(記憶體)與 Hard Disk(硬碟)的差別,並解釋什麼是 JSON 格式以及序列化 (Serialization) 的運作機制。

🎓 教師備課手冊 (Teacher Only)
  • 比較 RAM (揮發性) 與 Disk (持久性)。
  • 介紹 JSON 格式:List 映為 Array [...],Dict 映為 Object {...}
  • Django 銜接點:Web API 序列化 (Serialization) 與資料庫儲存。
💻 程式碼段落
import json

# Python 結構轉換為 JSON 字串 (Serialization)
data = [{"name": "珍奶", "price": 60}]
json_str = json.dumps(data, ensure_ascii=False, indent=4)
print("轉換後的 JSON 字串:\n", json_str)

# JSON 字串解析還原為 Python List/Dict (Deserialization)
parsed_data = json.loads(json_str)
print("還原後的型別:", type(parsed_data)) # 
⚠️ 常見陷阱與問題案例
  • 漏寫 ensure_ascii=False:印出中文時全變成 \u73cd\u4e76 亂碼轉義符。
🤖 單元 1 常見問題 AI 協作除錯提示詞

我在用 json.dumps() 轉化包含中文的字典時,結果印出了 \u73cd\u4e76 這種 unicode 碼。請教我如何在語法中加入 ensure_ascii=False 來保持中文字顯示。

⏰ 單元 2:檔案讀寫 (with open)(10:00 - 11:00)

📖 故事單元說明:自動關檔的保險箱筆記本

傳統寫檔如果忘記寫 f.close(),檔案會被作業系統鎖死甚至壞掉!with open(...) as f: 語法就像是「自動安全筆記本」,只要離開這塊程式碼,電腦就會自動幫你把筆記本合上蓋好。記得寫檔時永遠要加上 encoding="utf-8" 跨平台防亂碼防護!

💡 深度觀念:Context Manager (內容管理器) 與文字編碼

with 關鍵字引進了 Python 安全的資源管理機制:

  • 自動資源釋放:無論區塊內是否發生 Exception,with 語法皆保證觸發 __exit__ 自動關閉檔案控制代碼。
  • json.dump(obj, f):直接將物件序列化寫入檔案指標;json.load(f) 從檔案讀取還原。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

請說明為什麼在 Windows 系統用 Python 讀寫文字檔時一定要加上 encoding='utf-8',以及 with open 語法是如何自動幫我們安全關閉檔案 (close) 的。

🎓 教師備課手冊 (Teacher Only)
  • 採用 Context Manager:with open(...) as f: 最佳工程實踐。
  • 顯式指定 encoding="utf-8" 避免 Windows cp950 亂碼。
  • Django 銜接點:後端進行 I/O 連線與讀寫資料庫檔案。
💻 程式碼段落
import json

records = [{"name": "排骨便當", "price": 110}]

# 2-1: 寫入 JSON 檔案
with open("expenses.json", "w", encoding="utf-8") as f:
    json.dump(records, f, ensure_ascii=False, indent=4)
print("💾 資料已成功寫入 expenses.json!")

# 2-2: 從 JSON 檔案讀取
with open("expenses.json", "r", encoding="utf-8") as f:
    loaded_records = json.load(f)
print("📂 讀取出來的資料:", loaded_records)
⚠️ 常見陷阱與問題案例
  • 未指定 UTF-8 編碼:Windows 系統跳出 UnicodeDecodeError: 'cp950' codec can't decode... 崩潰。
🤖 單元 2 常見問題 AI 協作除錯提示詞

我在開啟檔案時跳出了 UnicodeDecodeError cp950 錯誤。請協助我檢查 open() 語法內部是否漏寫了 encoding='utf-8'。

⏰ 單元 3:開機自動讀檔與防呆(11:00 - 12:00)

🛰️ 故事單元說明:無縫開機探針檢測

當你第一次下載執行程式時,硬碟裡的 expenses.json 根本還不存在!如果直接強制讀取,程式就會跳出 FileNotFoundError 閃退。我們需要先發射雷達探針 os.path.exists("expenses.json"):若檔案存在就讀取;不存在就自動建立全新的空清單 []

💡 深度觀念:探針檢測與預防性程式設計

預防性程式設計的核心在於預先排除潛在的執行階段錯誤(Runtime Error):

  • os.path.exists(path):回傳布林值,檢查檔案或目錄路徑是否存在。
  • 無縫初始化 (Seamless Initialization):確保第一次軟體部署或檔案缺失時,系統能自動降級初始化不崩潰。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

我的 Python 程式一開機就因為 FileNotFoundError 崩潰。請教我如何用 os.path.exists() 先做檔案檢測,確保即使檔案不存在也能安全初始化。

🎓 教師備課手冊 (Teacher Only)
  • 探針檢測:os.path.exists("expenses.json") 避免開機破防。
  • 軟體初始化流:若不存在傳回 []
  • Django 銜接點:Django 啟動時自動檢查 DB 連線與 Migration 狀態。
💻 程式碼段落
import json
import os

DATA_FILE = "expenses.json"

def safe_load():
    # 檢查檔案是否存在
    if not os.path.exists(DATA_FILE):
        print("ℹ️ 未檢測到歷史檔案,初始化全新記帳本。")
        return []
    
    try:
        with open(DATA_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception as e:
        print(f"⚠️ 讀取失敗,原因:{e}")
        return []

records = safe_load()
print(f"系統順利啟動,目前包含 {len(records)} 筆紀錄。")
⚠️ 常見陷阱與問題案例
  • 無視檔案存在直接讀檔:跳出 FileNotFoundError: [Errno 2] No such file or directory: 'expenses.json'
🤖 單元 3 常見問題 AI 協作除錯提示詞

我在讀取 json 檔案時跳出了 FileNotFoundError。請協助我撰寫一段包含 os.path.exists() 判斷式的 safe_load() 函式來解決這個開機崩潰問題。

⏰ 單元 4:CRUD 刪除與修改實作(13:00 - 14:00)

🛠️ 故事單元說明:完整增刪查改大滿貫 (CRUD)

記錯金額或買錯東西怎麼辦?今天我們要解鎖所有軟體工程最核心的能力:**CRUD (Create 增、Read 查、Update 改、Delete 刪)**!特別注意:使用者看的編號是從 1 開始,但 Python 索引是從 0 開始,記得要做好 target_idx = user_input - 1 的換算喔!

💡 深度觀念:什麼是 CRUD 與人類/電腦編號轉換?

CRUD 代表持久化儲存的四大基本功能:

  • [C]reateappend() + 寫入檔案。[R]ead:走訪列印列表。
  • [U]pdate:修改指定索引項目 records[idx]["price"] = new_price
  • [D]eleterecords.pop(idx) 移除項目並自動同步存檔。
  • Off-by-One 差一轉換:將人類習慣的 1-based 輸入減 1 轉為電腦的 0-based 索引。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

向初學者解釋什麼是 CRUD,以及在 Python 清單中透過索引(index)進行刪除 pop() 與修改時,如何處理使用者輸入的編號與內部 0-based 索引的轉換。

🎓 教師備課手冊 (Teacher Only)
  • 完整 CRUD:Create (append), Read (for), Update (dict key), Delete (pop)。
  • Off-By-One 差一轉換:idx = user_input - 1
  • Django 銜接點:Django ORM 的 .save().delete()
💻 程式碼段落
records = [
    {"name": "珍奶", "price": 60},
    {"name": "便當", "price": 100}
]

# 刪除特定索引紀錄 (Delete)
del_idx = int(input("請輸入要刪除的編號 (1-based): ")) - 1

if 0 <= del_idx < len(records):
    deleted_item = records.pop(del_idx)
    print(f"🗑️ 已成功刪除:{deleted_item['name']}")
else:
    print("⚠️ 無效的編號!")
⚠️ 常見陷阱與問題案例
  • 未進行索引換算:使用者想刪第 1 筆輸入 1,結果刪除了第 2 筆 (索引 1 的項目)。
🤖 單元 4 常見問題 AI 協作除錯提示詞

我在實作刪除功能時,點選刪除第 1 筆結果程式卻刪除了第 2 筆資料。請幫我檢查是否有做 target_idx = user_input - 1 的轉換。

⏰ 單元 5:專案實作:資料備份機制(14:00 - 15:00)

📄 故事單元說明:老闆最愛的財務月報表 (.txt)

除了給電腦看的 JSON 資料外,老闆與使用者通常需要一份「可以直接列印閱讀的純文字報表」。這個單元我們會用 with open("report.txt", "w") 搭配走訪格式化,將記憶體中的帳務資料導出一份美觀的 `.txt` 財務月報!

💡 深度觀念:文字檔寫入與格式化報表

將資料結構加工輸出為人類可讀文件是軟體應用的重要一環:

  • f.write() 的換行關鍵:與 print() 不同,f.write() **不會自動換行**,必須手動加上 \n
  • 格式化排版:結合 f-string 置中或對齊語法,導出完美的純文字表格報表。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

我想寫一個 Python 函式將記帳清單轉成一份格式整齊的 .txt 月報表,包含項目、金額與總計,請給我優雅的範例程式碼並說明 f.write() 換行 (\n) 的注意事項。

🎓 教師備課手冊 (Teacher Only)
  • 將 JSON 加工導出純文字 `.txt` 報表。
  • 強調 f.write() 不會自動換行,需加 \n
  • Django 銜接點:Web 後台「匯出 CSV/PDF 報表」與資料庫備份機制。
💻 程式碼段落
records = [
    {"date": "2026-08-03", "name": "珍珠奶茶", "price": 60},
    {"date": "2026-08-03", "name": "排骨便當", "price": 110}
]

# 匯出排版報表
with open("report.txt", "w", encoding="utf-8") as f:
    f.write("=== 📊 財務月報表 ===\n")
    total = 0
    for r in records:
        f.write(f"• {r['date']} | {r['name']:<8} | ${r['price']} 元\n")
        total += r["price"]
    f.write(f"\n總累計消費:${total} 元\n")

print("📄 報表匯出成功!請查看 report.txt 檔案。")
⚠️ 常見陷阱與問題案例
  • 忘記手動換行:匯出的 `.txt` 檔案所有文字通通黏在同一行無法閱讀。
🤖 單元 5 常見問題 AI 協作除錯提示詞

我在用 f.write() 寫入 .txt 檔案時,導出的文字通通黏在同一行沒有換行。請幫我檢查是否漏寫了 \n 換行符號。

⏰ 單元 6:破壞性測試與容錯修復(15:00 - 16:00)

🚑 故事單元說明:黑盒子災難復原機制 (os.rename)

在現實世界中,檔案可能因為突然斷電或被不小心誤刪大括號而格式毀損!當 expenses.json 壞掉時,json.load() 會拋出 JSONDecodeError 當機。真正的工程師會寫出 **容錯修復 (Fault Tolerance)** 程式:自動捕捉這個錯誤,把壞掉的檔案改名備份(os.rename),並重置一個乾淨的全新帳本!

💡 深度觀念:系統容錯與災難復原 (Disaster Recovery)

高可靠度軟體具備良好的自我修復與降級運作能力:

  • json.decoder.JSONDecodeError:捕捉 JSON 格式語法毀損的特定例外。
  • 備份離場 os.rename("expenses.json", "corrupted.bak"):保護使用者壞掉的舊資料不被直接覆蓋覆滅。

🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):

🤖 AI 自主學習探索提示詞 (Prompt):

探索 Prompt (點擊複製)

如果 JSON 資料檔被意外損毀導致開機時跳出 JSONDecodeError,如何寫出包含捕捉此例外、自動用 os.rename() 備份舊檔,並重新初始化清單的容錯修復機制?

🎓 教師備課手冊 (Teacher Only)
  • 讓學生刻意刪除 expenses.json 內部大括號,觀察崩潰訊息。
  • 捕捉 json.JSONDecodeError,並用 os.rename() 安全離場備份。
  • Django 銜接點:資料庫 Migration 失敗防護與災難復原 (Disaster Recovery)。
💻 程式碼段落
import json
import os

DATA_FILE = "expenses.json"

def resilient_load():
    if not os.path.exists(DATA_FILE):
        return []
    
    try:
        with open(DATA_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    except json.JSONDecodeError:
        print("🚨 警告:檢測到 JSON 檔案格式毀損!")
        # 自動修復邏輯:將壞掉的檔案改名備份,並重置全新檔案
        os.rename(DATA_FILE, "expenses_corrupted.bak")
        print("🛠️ 已將損毀檔案備份為 expenses_corrupted.bak,並重置系統清單。")
        return []

records = resilient_load()
⚠️ 常見陷阱與問題案例
  • 未捕捉 JSONDecodeError:檔案損毀後開機必當機拋出 json.decoder.JSONDecodeError
🤖 單元 6 常見問題 AI 協作除錯提示詞

我的 JSON 檔案毀損導致程式開機跳出 json.decoder.JSONDecodeError。請教我如何用 except json.JSONDecodeError: 捕捉它並自動改名備份。