🚀 Day 3:打造「常駐型 CLI 記帳路由系統」
5 天全日營隊 (Day 3 / 總時數 6 Hours) | 打造持續監聽、具備路由分發與容錯的永動系統
📖 為什麼學 these?——「把腳本升級為不打烊 ATM」關卡冒險故事線
前兩天的程式只要印完結果就自動退出了,就像一家結完一次帳就關門的店。今天我們要讓理財咖啡館裝上 **永動引擎與路由分發站**,並學習如何擋下亂輸入的怪獸:
| 單元名稱 | 記帳程式關卡角色 | 為什麼這個單元不可或缺?(技術關聯) |
|---|---|---|
| 單元 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 專屬折扣試算或統計,體驗特例路由與權限分支控制! |
📌 課程前導與簡介說明
- 營隊定位:讓程式從「一次性腳本」演進為**「不間斷監聽」的服務系統**。
- 高中生心理特性:輸入錯誤金額時不再崩潰會讓學生體會「工程師如何防當機」。
- Django 銜接點:選單分發機制完美對應 Django URL Router (
urls.py) 與 View 的分發關係。
歡迎來到 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):
請向初學者解釋 Python 的 if-elif-else 語法,以及一個等於 (=) 和兩個等於 (==) 的差異,並說明短路評價 (Short-circuit evaluation) 在 and / or 中的運作機制。
- 分支邏輯判斷與邏輯運算子(`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。
我在 Python 的 if 判斷式裡面寫了 = 結果跳出 SyntaxError。請幫我說明一個等於 (=) 與兩個等於 (==) 的差異,並指導我修復此錯誤。
⏰ 單元 2:無限迴圈 (while True)(10:00 - 11:00)
記帳程式不能算完一筆就關閉。while True: 就像是理財咖啡館的「永動發電機」,讓程式維持常駐監聽狀態!我們可以在內部用 break 手動踩煞車退出,或者用 continue 立刻跳過後續指令、進入下一輪選單。
所有伺服器(Server)與桌面軟體的底層都是基於常駐迴圈運行的:
break語法:立即破壞當前迴圈結構,強制跳出至迴圈外的第一行。continue語法:放棄該次迭代剩餘程式碼,直接重新測試條件回到迴圈開頭。- 急救快捷鍵
Ctrl + C:在 Terminal 中觸發KeyboardInterrupt,強制結束死迴圈。
🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):
🤖 AI 自主學習探索提示詞 (Prompt):
請說明 Python 中 while True 的作用,並詳細比對 break 與 continue 的差異,最後告訴我陷入死迴圈時如何在 VS Code 終端機按 Ctrl+C 強制結束。
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解救。
我的 Python 程式陷入了死迴圈停不下來!請告訴我如何在 VS Code 終端機中強制關閉它,並幫我檢查 while 迴圈裡缺少了哪一個 break 離開條件。
⏰ 單元 3:終端機選單與路由分發(11:00 - 12:00)
就像提款機 (ATM) 的螢幕選單一樣,顧客可以自由選擇「1. 新增」、「2. 查看」、「3. 離開」。使用者輸入選項後,由 if choice == "1": 將程式導向不同的功能區塊,這就是軟體工程中極為重要的 **「路由分發 (Routing Dispatcher)」**!
路由的作用是將使用者的請求(Request / Input)對應到處理該請求的邏輯分支:
- 字串選單最安全:選單輸入比對時建議直接用字串
"1",避免轉型數字時輸入英文產生崩潰。 - 對應 Web 網址分發:CLI 的
choice == "1"相當於網頁中的網址路徑/add/。
🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):
🤖 AI 自主學習探索提示詞 (Prompt):
請用網頁網址分發(URL Dispatcher)的概念,說明 CLI 選單中的 if choice == '1' 是如何扮演路由分發角色的,並示範結構清晰的 CLI 選單範例。
- 設計 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。
我在選單輸入字母或無效字元時程式直接跳出 ValueError 崩潰。請教我如何把選項當作純字串比對,以提升 CLI 介面的容錯率。
⏰ 單元 4:整合 Day 1-2 功能(13:00 - 14:00)
現在我們要將 Day 1 的單筆輸入、Day 2 的 List of Dicts 購物車與 for 迴圈總帳加總,打包塞進剛剛建好的選單路由中。但請特別注意:**`records = []` 資料清單必須宣告在 `while True` 外部**!否則每一次循環帳本都會被重新清空喔!
在常駐應用程式中,變數宣告的位置決定了資料的生命週期:
- 常駐狀態保持 (State):全域容器(如
records)必須宣告在迴圈外,確保跨選項操作時資料不會消失。 - LEGB 作用域規則:Python 查找變數依序為 Local $\rightarrow$ Enclosing $\rightarrow$ Global $\rightarrow$ Built-in。
🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):
🤖 AI 自主學習探索提示詞 (Prompt):
我把變數清單寫在 while 迴圈內部,結果每次新增資料舊的資料都被清空了!請幫我解釋區域變數與全域變數(Scope)的概念並示範修正方式。
- 將前兩天功能嵌入選單分支。
- 強調變數作用域(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內部,導致舊紀錄不斷流失。
我每次點選新增消費時,先前輸入的記帳資料都不見了。請幫我檢查 records 清單初始化宣告的位置是否錯誤地放在迴圈內部。
⏰ 單元 5:異常處理 (try-except)(14:00 - 15:00)
使用者不是工程師,常常會在輸入金額時不小心輸入中文「一百」。這時 int() 轉型會直接觸發 ValueError 當機!用 try-except 語法把危險的程式碼圍起來,當出事時由 except ValueError: 攔截並印出溫柔警告,讓程式 **「降級運作而不當機」**!
Python 具備結構化的例外處理機制,確保程式不會因為無效輸入直接中斷:
- 指定特定 Exception 類型:永遠指定明確的例外類型(如
except ValueError:),避免寫出捕捉萬物的裸except:(會導致連 Ctrl+C 也無法結束)。 else與finally區塊:else在無例外時執行,finally無論是否出錯皆會執行。
🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):
🤖 AI 自主學習探索提示詞 (Prompt):
請向初學者解釋 try-except 的運作原理,並說明為什麼捕捉特定的 Exception(例如 ValueError)會比寫一個空泛的 except 更好,最後示範用 try-except 進行輸入防呆。
- 使用
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 被意外掩蓋。
我想在使用者輸入非數字金額時防止程式崩潰,請教我如何只將 int(input()) 那行寫入 try 區塊,並用 except ValueError: 進行溫柔的錯誤提示。
⏰ 單元 6:遊戲化實作:密碼彩蛋(15:00 - 16:00)
許多軟體都藏有開發者的秘密彩蛋(Easter Egg)!在我們的選單邏輯中加入未列出的隱藏指令(如 99),當觸發暗號時執行 VIP 打折試算或顯示隱藏統計,體驗特例路由與權限控制的樂趣!
在網路軟體架構中,特例路由常用於管理員通道或特定權限使用者:
- 隱藏路由與權限分流:例如輸入管理者金鑰時開啟專屬功能的機制。
- 對應 Web 後台:概念對應 Django Admin 後台路由 (
/admin/) 與使用者權限判斷 (is_staff)。
🔗 Google 搜尋關鍵字 (依相關度排序,紫羅蘭標記為進階內容,點擊開啟新分頁):
🤖 AI 自主學習探索提示詞 (Prompt):
我想要在 CLI 記帳程式中加入一個暗號彩蛋,當使用者輸入 99 時會觸發隱藏的全品項打折計算。請幫我設計這段程式碼並確保語法格式整齊。
- 設計隱藏指令(如
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排版。
我在計算打折彩蛋價格時,算出來的數字印出了長小數點 (例如 80.0000001)。請教我如何用 int() 或 f-string 格式化限制為整數。