🚀 Day 3:打造「常駐型 CLI 記帳路由系統」

5 天全日營隊 (Day 3 / 總時數 6 Hours) | 打造持續監聽、具備路由分發與容錯的永動系統

📖 為什麼學 these?——「把腳本升級為不打烊 ATM」關卡冒險故事線

💡 故事情境:理財咖啡館升級!打造 24 小時持續監聽的記帳服務系統

前兩天的程式只要印完結果就自動退出了,就像一家結完一次帳就關門的店。今天我們要讓理財咖啡館裝上 **永動引擎與路由分發站**,並學習如何擋下亂輸入的怪獸:

單元名稱 記帳程式關卡角色 為什麼這個單元不可或缺?(技術關聯)
單元 1:if-elif-else 邏輯判斷 🚦 分線路口指示牌 根據金額大小或開銷分類做出不同的決策處理(如:超過 1000 元觸發高額預警)。
單元 2:while True 無限迴圈 🔄 24 小時不打烊營業引擎 使用 while True: 保持程式常駐運作,搭配 break 離開與 continue 跳過,掌控程式生命週期。
單元 3:選單與路由分發 🗺️ 門市導覽大廳 (URL Router) 使用者輸入選項導向對應程式區塊(按 1 新增、按 2 查看),概念完美對應網頁 URL Router (urls.py)!
單元 4:整合 Day 1-2 功能 🛠️ 業務模組大合體 (Views) 將前兩天的資料收集、List of Dicts 寫入與走訪統計,嵌入選單分支中,組合為完整的系統流。
單元 5:try-except 異常處理 🛡️ 金剛不壞輸入防護牆 使用者輸入中文金額時不再閃退當機!透過 try-except 捕捉 ValueError,實現安全容錯。
單元 6:密碼彩蛋小遊戲 🎁 開發者秘密通道 (VIP Route) 輸入隱藏指令(如 99)觸發 VIP 專屬折扣試算或統計,體驗特例路由與權限分支控制!

📌 課程前導與簡介說明

🎓 教師心法與教學策略 (Teacher Only)
  • 營隊定位:讓程式從「一次性腳本」演進為**「不間斷監聽」的服務系統**。
  • 高中生心理特性:輸入錯誤金額時不再崩潰會讓學生體會「工程師如何防當機」。
  • Django 銜接點:選單分發機制完美對應 Django URL Router (urls.py) 與 View 的分發關係。
🌟 學生自主學習指引 (Student Self-Study)

歡迎來到 Day 3!今天我們要讓你的記帳程式「永遠保持連線」!請閱讀每個單元的 **故事單元說明**,點擊 **延伸閱讀** 展開深層觀念與 Google 搜尋,並善用每區塊的 **AI 協作除錯提示詞**!

💻 Day 3 專案總成果:常駐型 CLI 記帳路由系統

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

"""
 Day 3 最終成果:常駐型 CLI 記帳路由系統 (帶防呆與彩蛋)
 核心概念:while True 無限迴圈 -> if-elif 路由分發 (urls.py 雛形) -> try-except 容錯
"""
import datetime

records = [
    {"name": "珍珠奶茶", "price": 60, "category": "餐飲", "date": "2026-08-03"},
    {"name": "Python 營隊講義", "price": 300, "category": "學習", "date": "2026-08-03"}
]

# 路由監聽主迴圈 (模擬 Web Server Listen Lifecycle)
while True:
    print("\n" + "=" * 40)
    print("      🚀 酷炫 CLI 記帳系統選單 🚀      ")
    print("=" * 40)
    print("  [1] ➕ 新增消費紀錄")
    print("  [2] 📊 查看所有明細與統計")
    print("  [3] 🔍 依分類搜尋紀錄")
    print("  [4] 🚪 離開系統")
    print("=" * 40)

    choice = input("👉 請輸入功能選項 (1-4): ").strip()

    # 路由分發 (模擬 Django urls.py 與 views 分發)
    if choice == "1":
        print("\n--- ➕ 新增消費紀錄 ---")
        item_name = input("👉 請輸入品項名稱: ").strip()
        if not item_name:
            print("⚠️ 名稱不能為空!")
            continue

        # 防呆機制 (Input Validation / Error Handling)
        try:
            price = int(input("👉 請輸入金額 (TWD): "))
        except ValueError:
            print("❌ 錯誤:金額必須是純數字!已取消新增。")
            continue

        category = input("👉 請輸入分類 (預設: 一般): ").strip()
        if not category:
            category = "一般"

        today_str = datetime.date.today().strftime("%Y-%m-%d")
        records.append({
            "name": item_name,
            "price": price,
            "category": category,
            "date": today_str
        })
        print(f"✅ 成功記錄:{item_name} ${price} 元 [{category}]")

    elif choice == "2":
        print("\n--- 📊 帳務明細與統計列表 ---")
        if not records:
            print("📭 目前尚無任何記帳紀錄!")
            continue

        total = 0
        for idx, item in enumerate(records, start=1):
            print(f"[{idx}] {item['date']} | {item['name']:<10} | ${item['price']:<5} | [{item['category']}]")
            total += item["price"]
        print("-" * 40)
        print(f"💵 累計總消費金額:${total} 元")

    elif choice == "3":
        print("\n--- 🔍 依分類搜尋紀錄 ---")
        cat_query = input("👉 請輸入欲搜尋的分類: ").strip()
        matched = [r for r in records if r["category"] == cat_query]
        
        if matched:
            subtotal = 0
            for r in matched:
                print(f" • {r['name']}: ${r['price']} 元 ({r['date']})")
                subtotal += r["price"]
            print(f"🎯 分類「{cat_query}」小計:${subtotal} 元")
        else:
            print(f"⚠️ 找不到分類為「{cat_query}」的紀錄。")

    elif choice == "4":
        print("\n👋 感謝使用!系統已安全關閉。再見!")
        break

    elif choice == "99":
        # 隱藏彩蛋路由
        print("\n🎉 [VIP 彩蛋模式] 觸發打折試算!全體消費享 9 折優惠:")
        for r in records:
            print(f"  ✨ {r['name']} 優惠價: ${int(r['price'] * 0.9)} 元")

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

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

"""
 🎯 Day 3 自主挑戰:請將下方的 ____ 填入正確的 while、if 路由與 try-except 語法!
"""
records = []

# 1. 讓程式保持常駐說聽的無限迴圈
while ____: # 提示:代表真值的布林關鍵字
    print("\n[1] 新增消費  [2] 列出明細  [3] 離開系統")
    choice = input("👉 請選擇選項: ").strip()

    # 2. 路由分發判斷
    if choice == "1":
        item = input("品項: ")
        # 3. 輸入防呆保護
        try:
            price = ____(input("金額: ")) # 提示:轉型數字
            records.append({"name": item, "price": price})
            print("✅ 已新增!")
        ____ ValueError: # 提示:捕捉例外的關鍵字
            print("❌ 金額輸入錯誤,必須為純數字!")

    elif choice == "2":
        for r in records:
            print(f"• {r['name']}: ${r['price']}")

    elif choice == "3":
        print("👋 離開系統!")
        ____ # 提示:跳出迴圈的關鍵字

    elif choice == "99": # 隱藏彩蛋
        print("🎉 發現彩蛋!")

    else:
        print("⚠️ 選項無效,請重新輸入!")

⏰ 單元 1:邏輯判斷 (if-elif-else)(09:00 - 10:00)

🚦 故事單元說明:分線路口指示牌

當記帳程式遇到不同的資料時,需要做出不同的反應。if-elif-else 就像是「道路指示牌」!你可以組合邏輯運算子,例如當花費大於 1000 元且分類是餐飲時(price > 1000 and cat == "餐飲"),系統自動發出紅燈預警。記住:比較相等要用 **兩個等於 (==)**,一個等於(=)是指定數值喔!

💡 深度觀念:邏輯運算子與短路評價

Python 在處理條件邏輯(`and`, `or`, `not`)時具備高效率的 **短路評價 (Short-circuit Evaluation)** 特性:

  • and 短路:若第一個條件為 False,Python 會立刻判定結果為 False,不會執行第二個條件。
  • or 短路:若第一個條件為 True,Python 會立刻判定結果為 True,跳過後續檢測。
  • 防範布林轉型陷阱:在 Python 中,`0`、`""` (空字串)、`[]` (空清單) 皆會被判定為 False

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

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

探索 Prompt (點擊複製)

請向初學者解釋 Python 的 if-elif-else 語法,以及一個等於 (=) 和兩個等於 (==) 的差異,並說明短路評價 (Short-circuit evaluation) 在 and / or 中的運作機制。

🎓 教師備課手冊 (Teacher Only)
  • 分支邏輯判斷與邏輯運算子(`and`, `or`, `not`)。
  • 注意學生易混淆 `=` (指定) 與 `==` (比較)。
  • Django 銜接點:商業邏輯分支(Business Logic)與權限驗證。
💻 程式碼段落
price = 150
category = "餐飲"

# 多重邏輯條件分支
if price > 100 and category == "餐飲":
    print("🚨 這是一筆高額餐飲支出!")
elif price > 100:
    print("⚠️ 高額支出(非餐飲)")
else:
    print("✅ 一般正常開銷")
⚠️ 常見陷阱與問題案例
  • 等於號語法錯誤:寫成 if category = "餐飲": $\rightarrow$ 報錯 SyntaxError: invalid syntax
🤖 單元 1 常見問題 AI 協作除錯提示詞

我在 Python 的 if 判斷式裡面寫了 = 結果跳出 SyntaxError。請幫我說明一個等於 (=) 與兩個等於 (==) 的差異,並指導我修復此錯誤。

⏰ 單元 2:無限迴圈 (while True)(10:00 - 11:00)

🔄 故事單元說明:24 小時不打烊營業引擎

記帳程式不能算完一筆就關閉。while True: 就像是理財咖啡館的「永動發電機」,讓程式維持常駐監聽狀態!我們可以在內部用 break 手動踩煞車退出,或者用 continue 立刻跳過後續指令、進入下一輪選單。

💡 深度觀念:常駐監聽與 Event Loop 觀念

所有伺服器(Server)與桌面軟體的底層都是基於常駐迴圈運行的:

  • break 語法:立即破壞當前迴圈結構,強制跳出至迴圈外的第一行。
  • continue 語法:放棄該次迭代剩餘程式碼,直接重新測試條件回到迴圈開頭。
  • 急救快捷鍵 Ctrl + C:在 Terminal 中觸發 KeyboardInterrupt,強制結束死迴圈。

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

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

探索 Prompt (點擊複製)

請說明 Python 中 while True 的作用,並詳細比對 break 與 continue 的差異,最後告訴我陷入死迴圈時如何在 VS Code 終端機按 Ctrl+C 強制結束。

🎓 教師備課手冊 (Teacher Only)
  • while True: 建立永動監聽;break 為關鍵煞車。
  • 預防死迴圈:帶學生演練 Ctrl + C 強制在中斷。
  • Django 銜接點:Web Server (如 Gunicorn) 常駐 Event Loop 監聽連線。
💻 程式碼段落
# 2-1: 常駐運算與 break 手動離開
while True:
    cmd = input("請輸入指令 (輸入 'q' 離開): ").strip()
    if cmd == "q":
        print("離開迴圈!")
        break
    elif cmd == "":
        print("不能輸入空白!")
        continue
    
    print(f"您輸入的指令是:{cmd}")
⚠️ 常見陷阱與問題案例
  • 忘記寫 break 導致死迴圈:畫面無限快速滾動,程式停不下來 $\rightarrow$ 按 Ctrl + C 解救。
🤖 單元 2 常見問題 AI 協作除錯提示詞

我的 Python 程式陷入了死迴圈停不下來!請告訴我如何在 VS Code 終端機中強制關閉它,並幫我檢查 while 迴圈裡缺少了哪一個 break 離開條件。

⏰ 單元 3:終端機選單與路由分發(11:00 - 12:00)

🗺️ 故事單元說明:門市導覽大廳 (URL Router)

就像提款機 (ATM) 的螢幕選單一樣,顧客可以自由選擇「1. 新增」、「2. 查看」、「3. 離開」。使用者輸入選項後,由 if choice == "1": 將程式導向不同的功能區塊,這就是軟體工程中極為重要的 **「路由分發 (Routing Dispatcher)」**!

💡 深度觀念:路由分發設計模式 (Routing Pattern)

路由的作用是將使用者的請求(Request / Input)對應到處理該請求的邏輯分支:

  • 字串選單最安全:選單輸入比對時建議直接用字串 "1",避免轉型數字時輸入英文產生崩潰。
  • 對應 Web 網址分發:CLI 的 choice == "1" 相當於網頁中的網址路徑 /add/

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

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

探索 Prompt (點擊複製)

請用網頁網址分發(URL Dispatcher)的概念,說明 CLI 選單中的 if choice == '1' 是如何扮演路由分發角色的,並示範結構清晰的 CLI 選單範例。

🎓 教師備課手冊 (Teacher Only)
  • 設計 UI 選單:1.新增 2.查看 3.離開。
  • 選單輸入建議用字串(choice == "1"),容錯率最高。
  • Django 銜接點:網址分發器 urls.py
💻 程式碼段落
# 3-1: 路由系統結構雛形
while True:
    print("\n--- 系統選單 ---")
    print("1. 執行功能 A")
    print("2. 執行功能 B")
    print("3. 結束系統")
    
    choice = input("請選擇: ").strip()

    if choice == "1":
        print("➡️ 進入 [功能 A] 處理流程...")
    elif choice == "2":
        print("➡️ 進入 [功能 B] 處理流程...")
    elif choice == "3":
        print("👋 系統正常退出。")
        break
    else:
        print("⚠️ 無效選項,請重新輸入!")
⚠️ 常見陷阱與問題案例
  • 選單誤用 int 轉型:寫成 choice = int(input()) 導致輸入字母時蹦出 ValueError
🤖 單元 3 常見問題 AI 協作除錯提示詞

我在選單輸入字母或無效字元時程式直接跳出 ValueError 崩潰。請教我如何把選項當作純字串比對,以提升 CLI 介面的容錯率。

⏰ 單元 4:整合 Day 1-2 功能(13:00 - 14:00)

🛠️ 故事單元說明:業務模組大合體 (Views)

現在我們要將 Day 1 的單筆輸入、Day 2 的 List of Dicts 購物車與 for 迴圈總帳加總,打包塞進剛剛建好的選單路由中。但請特別注意:**`records = []` 資料清單必須宣告在 `while True` 外部**!否則每一次循環帳本都會被重新清空喔!

💡 深度觀念:狀態保持 (State Maintenance) 與 LEGB 作用域

在常駐應用程式中,變數宣告的位置決定了資料的生命週期:

  • 常駐狀態保持 (State):全域容器(如 records)必須宣告在迴圈外,確保跨選項操作時資料不會消失。
  • LEGB 作用域規則:Python 查找變數依序為 Local $\rightarrow$ Enclosing $\rightarrow$ Global $\rightarrow$ Built-in。

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

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

探索 Prompt (點擊複製)

我把變數清單寫在 while 迴圈內部,結果每次新增資料舊的資料都被清空了!請幫我解釋區域變數與全域變數(Scope)的概念並示範修正方式。

🎓 教師備課手冊 (Teacher Only)
  • 將前兩天功能嵌入選單分支。
  • 強調變數作用域(Scope):records = [] 必須在迴圈外。
  • Django 銜接點:View 視圖函數處理請求並存取模型數據。
💻 程式碼段落
records = []

while True:
    print("\n[1] 新增消費  [2] 列出總帳  [3] 離開")
    op = input("請選擇:").strip()

    if op == "1":
        name = input("品項: ")
        price = int(input("金額: "))
        records.append({"name": name, "price": price})
        print("✅ 已新增!")
    elif op == "2":
        total = 0
        for r in records:
            print(f"• {r['name']}: ${r['price']}")
            total += r["price"]
        print(f"總計:${total} 元")
    elif op == "3":
        break
⚠️ 常見陷阱與問題案例
  • 清單在迴圈內重置:把 records = [] 寫在 while True 內部,導致舊紀錄不斷流失。
🤖 單元 4 常見問題 AI 協作除錯提示詞

我每次點選新增消費時,先前輸入的記帳資料都不見了。請幫我檢查 records 清單初始化宣告的位置是否錯誤地放在迴圈內部。

⏰ 單元 5:異常處理 (try-except)(14:00 - 15:00)

🛡️ 故事單元說明:金剛不壞輸入防護牆

使用者不是工程師,常常會在輸入金額時不小心輸入中文「一百」。這時 int() 轉型會直接觸發 ValueError 當機!用 try-except 語法把危險的程式碼圍起來,當出事時由 except ValueError: 攔截並印出溫柔警告,讓程式 **「降級運作而不當機」**!

💡 深度觀念:例外處理階層 (Exception Hierarchy)

Python 具備結構化的例外處理機制,確保程式不會因為無效輸入直接中斷:

  • 指定特定 Exception 類型:永遠指定明確的例外類型(如 except ValueError:),避免寫出捕捉萬物的裸 except:(會導致連 Ctrl+C 也無法結束)。
  • elsefinally 區塊else 在無例外時執行,finally 無論是否出錯皆會執行。

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

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

探索 Prompt (點擊複製)

請向初學者解釋 try-except 的運作原理,並說明為什麼捕捉特定的 Exception(例如 ValueError)會比寫一個空泛的 except 更好,最後示範用 try-except 進行輸入防呆。

🎓 教師備課手冊 (Teacher Only)
  • 使用 try-except 捕捉 ValueError
  • 警惕學生避免裸寫 except:(會連 Ctrl+C 也捕捉阻斷)。
  • Django 銜接點:400 Bad Request 表單欄位驗證防護。
💻 程式碼段落
# 防呆範例:安全的數字輸入機制
while True:
    price_input = input("👉 請輸入消費金額: ").strip()
    
    try:
        price = int(price_input)
        print(f"✅ 輸入成功,金額為:${price} 元")
        break
    except ValueError:
        print("❌ 錯誤:請輸入純數字!不要輸入中文或英文符號。")
⚠️ 常見陷阱與問題案例
  • 無特定 Try 區塊範圍:把過多不相關的程式碼通通塞進 `try` 區塊,導致真正的邏輯 Bug 被意外掩蓋。
🤖 單元 5 常見問題 AI 協作除錯提示詞

我想在使用者輸入非數字金額時防止程式崩潰,請教我如何只將 int(input()) 那行寫入 try 區塊,並用 except ValueError: 進行溫柔的錯誤提示。

⏰ 單元 6:遊戲化實作:密碼彩蛋(15:00 - 16:00)

🎁 故事單元說明:開發者秘密通道 (VIP Route)

許多軟體都藏有開發者的秘密彩蛋(Easter Egg)!在我們的選單邏輯中加入未列出的隱藏指令(如 99),當觸發暗號時執行 VIP 打折試算或顯示隱藏統計,體驗特例路由與權限控制的樂趣!

💡 深度觀念:特例路由與權限控制邏輯

在網路軟體架構中,特例路由常用於管理員通道或特定權限使用者:

  • 隱藏路由與權限分流:例如輸入管理者金鑰時開啟專屬功能的機制。
  • 對應 Web 後台:概念對應 Django Admin 後台路由 (/admin/) 與使用者權限判斷 (is_staff)。

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

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

探索 Prompt (點擊複製)

我想要在 CLI 記帳程式中加入一個暗號彩蛋,當使用者輸入 99 時會觸發隱藏的全品項打折計算。請幫我設計這段程式碼並確保語法格式整齊。

🎓 教師備課手冊 (Teacher Only)
  • 設計隱藏指令(如 99)。
  • 增加課堂趣味性與權限邏輯思考。
  • Django 銜接點:Django Admin 特殊路由 /admin/ 與權限驗證。
💻 程式碼段落
records = [{"name": "排骨便當", "price": 100}]

choice = input("選擇選項 (1-3): ").strip()

if choice == "1":
    print("一般功能...")
elif choice == "777": # 隱藏彩蛋
    print("🎉 恭喜發現開發者彩蛋!觸發全品項 8 折:")
    for r in records:
        print(f"  • {r['name']} 特價: ${r['price'] * 0.8:.0f} 元")
⚠️ 常見陷阱與問題案例
  • 浮點數格式未處理:打折計算 price * 0.8 產生浮點數小數點,未用 int():.0f 排版。
🤖 單元 6 常見問題 AI 協作除錯提示詞

我在計算打折彩蛋價格時,算出來的數字印出了長小數點 (例如 80.0000001)。請教我如何用 int() 或 f-string 格式化限制為整數。