2026年11月2日 星期一

Python 例外處理實戰:飲料店 POS 點餐系統

Python 程式實習|整合實作

Python 例外處理實戰:打造不怕輸入錯誤的飲料店 POS 點餐系統

顧客可能輸入錯誤的商品代碼、把「兩杯」當成數字、輸入負數杯數,甚至付款不足。真正可靠的程式,不是永遠不會遇到錯誤,而是能辨識可預期的錯誤,給出提示並讓使用者重新操作。本篇從生活情境出發,整合字典、迴圈、函式、類別、例外處理,完成可在終端機執行的飲料店 POS。

一、情境與學習目標

店裡販售紅茶(30 元)、珍珠奶茶(50 元)、綠茶(35 元)。顧客可重複選購商品、輸入杯數,輸入 0 結帳;系統列出明細、計算總額、收款及找零。

完成後,你應能回答:哪些錯誤應使用 try/except?哪些情況要用 if 驗證?為什麼把輸入流程與訂單資料分開設計?

二、先看系統流程

顯示菜單輸入代碼驗證資料加入訂單結帳收款輸入錯誤 → 重新輸入繼續點餐 → 回到菜單

三、第一步:用字典建立菜單

MENU = {"1": ("紅茶", 30),
        "2": ("珍珠奶茶", 50),
        "3": ("綠茶", 35)}

商品代碼使用字串,商品資料以 (名稱, 單價) 表示。透過 MENU[code] 可以找到對應商品;如果代碼不存在,應該提供清楚的錯誤訊息,而不是讓程式直接崩潰。

四、第二步:先處理最常見的輸入錯誤

def read_quantity():
    while True:
        try:
            quantity = int(input("數量(杯):"))
            if quantity <= 0:
                raise ValueError("數量必須大於 0")
            return quantity
        except ValueError as error:
            print(f"輸入錯誤:{error},請重新輸入。")

int() 遇到「兩杯」會產生 ValueError;數量為 0 或負數則是我們定義的商業規則,使用 raise ValueError(...) 主動拒絕。兩者都由同一個 except 處理,再用迴圈重新詢問。

五、第三步:用類別管理訂單

class Order:
    def __init__(self):
        self.items = []

    def add_item(self, code, quantity):
        if code not in MENU:
            raise KeyError("找不到這個商品代碼")
        if quantity <= 0:
            raise ValueError("數量必須大於 0")
        name, price = MENU[code]
        self.items.append((name, price, quantity))

    def total(self):
        return sum(price * qty for _, price, qty in self.items)

Order 負責保存訂單和計算金額;add_item() 再次檢查資料,避免其他程式繞過輸入介面時加入不合理的商品。這就是封裝與例外處理的結合。

六、第四步:結帳時處理付款不足

def checkout(order):
    total = order.total()
    while True:
        try:
            paid = int(input(f"應收 {total} 元,請輸入實收金額:"))
            if paid < total:
                raise ValueError("實收金額不足")
        except ValueError as error:
            print(f"付款失敗:{error},請重新輸入。")
        else:
            print(f"找零:{paid - total}元")
            print("交易完成,謝謝光臨!")
            return

這裡的 else 只有在 try 沒有產生例外時才執行。實收金額不足時不會結帳,而是要求重新輸入。這是課堂示範的現金收款模型,尚未包含折扣、發票或交易紀錄。

七、完整可執行程式

將以下程式存成 drink_pos.py,在終端機輸入 python drink_pos.py 執行。不需要額外安裝套件。

"""飲料店 POS:以教學為目的的命令列範例。"""
MENU = {"1": ("紅茶", 30), "2": ("珍珠奶茶", 50), "3": ("綠茶", 35)}

class Order:
    def __init__(self):
        self.items = []

    def add_item(self, code, quantity):
        if code not in MENU:
            raise KeyError("找不到這個商品代碼")
        if quantity <= 0:
            raise ValueError("數量必須大於 0")
        name, price = MENU[code]
        self.items.append((name, price, quantity))

    def total(self):
        return sum(price * qty for _, price, qty in self.items)

    def show_receipt(self):
        print("\n=== 訂單明細 ===")
        for name, price, qty in self.items:
            print(f"{name} {qty}杯 × {price}元 = {price * qty}元")
        print(f"合計:{self.total()}元")


def read_quantity():
    while True:
        try:
            quantity = int(input("數量(杯):"))
            if quantity <= 0:
                raise ValueError("數量必須大於 0")
            return quantity
        except ValueError as error:
            print(f"輸入錯誤:{error},請重新輸入。")


def checkout(order):
    total = order.total()
    while True:
        try:
            paid = int(input(f"應收 {total} 元,請輸入實收金額:"))
            if paid < total:
                raise ValueError("實收金額不足")
        except ValueError as error:
            print(f"付款失敗:{error},請重新輸入。")
        else:
            print(f"找零:{paid - total}元")
            print("交易完成,謝謝光臨!")
            return


def main():
    order = Order()
    print("=== Python 飲料店 POS ===")
    while True:
        print("\n菜單:")
        for code, (name, price) in MENU.items():
            print(f"{code}. {name} {price}元")
        code = input("商品代碼(0 結帳):").strip()
        if code == "0":
            if not order.items:
                print("目前沒有商品,結束程式。")
                return
            break
        try:
            if code not in MENU:
                raise KeyError("沒有這個商品代碼")
            qty = read_quantity()
            order.add_item(code, qty)
        except KeyError as error:
            print(f"選購失敗:{error.args[0]},請重新選擇。")
        else:
            print("商品已加入訂單。")
    order.show_receipt()
    checkout(order)


if __name__ == "__main__":
    main()

八、執行測試:讓錯誤真的發生

=== Python 飲料店 POS ===
菜單:
1. 紅茶 30元
2. 珍珠奶茶 50元
3. 綠茶 35元
商品代碼(0 結帳):9
選購失敗:沒有這個商品代碼,請重新選擇。

商品代碼(0 結帳):2
數量(杯):兩杯
輸入錯誤:invalid literal for int() with base 10: '兩杯',請重新輸入。
數量(杯):-1
輸入錯誤:數量必須大於 0,請重新輸入。
數量(杯):2
商品已加入訂單。

商品代碼(0 結帳):0
=== 訂單明細 ===
珍珠奶茶 2杯 × 50元 = 100元
合計:100元
應收 100 元,請輸入實收金額:50
付款失敗:實收金額不足,請重新輸入。
應收 100 元,請輸入實收金額:200
找零:100元
交易完成,謝謝光臨!

上方為示意互動紀錄,實際終端機畫面會依輸入而異。

九、程式中的例外處理與條件判斷

情境處理方式原因
輸入「兩杯」except ValueErrorint() 無法轉換
輸入 -1 杯if + raise ValueError違反購買數量規則
商品代碼 9raise KeyError + except菜單沒有此商品
付款不足if + raise ValueError違反付款規則
正常付款else沒有發生例外才完成交易
重要觀念:不是所有錯誤都應該用 except 處理。程式設計時,先以條件判斷描述正常的業務規則,再用例外表示無法完成操作的情況。也不要使用 except: pass 把問題全部隱藏。

十、三階段 AI 協作實習

No-AI|獨立理解

不使用 AI,畫出點餐流程,指出至少三個可能發生錯誤的地方,並說明 while、try、except 各自負責什麼。

AI Pair|共同改善

請 AI 協助新增「同商品累計杯數」及「輸入 q 取消交易」功能。學生必須自行測試空訂單、非法代碼與取消操作。

Challenge AI|驗證 AI

要求 AI 將 POS 擴充成「庫存有限」的版本。測試庫存不足、輸入 0 杯、超額付款與連續輸入錯誤,並檢查是否發生先扣庫存、後來卻結帳失敗的資料不一致問題。

十一、延伸挑戰與教學反思

本範例刻意保持精簡,讓學生先理解錯誤發生時如何恢復。下一步可增加商品庫存、訂單編號、檔案儲存、GUI 或 Django 網頁介面。正式 POS 還需要考慮金額精度、資料持久化、交易一致性與權限管理。

AI 可以快速寫出 try/except,但真正的程式設計能力,是判斷什麼情況應重試、拒絕、取消或回復資料。當學生能清楚回答這些問題,就不只是會寫語法,而是開始學會設計可靠的系統。

Python 程式實習|生活化教學 × 物件導向 × 例外處理 × AI 協作

沒有留言:

張貼留言