TEMPEDU · 程式判讀實作 · 第01課
先看物件與順序:共享串列、近鄰投票
預估 40 分鐘|閱讀與預測約 15 分鐘,其餘用於逐段執行、變形練習與修正。先備:Python 串列、字典、函式及基本 pandas 操作。每段完整程式都重建資料,不依賴前一課變數。
- 用輸入、型別、順序、輸出、限制五步寫讀碼紀錄
- 區分修改物件與重新指定變數
- 算出一維 KNN 的距離、近鄰與投票結果
01先讀任務,再預測結果
曦禾通路的分析員寫了一個標記函式,兩張不同訂單卻出現相同的累積標記。另一個程式用相近的配送距離推測處理類別。這兩段程式都不長,但若只看最後一行,容易漏掉共享物件及排序規則。本課資料與商家均為虛構。
建議操作:先把「輸入→型別→順序→輸出→限制」寫在紙上或筆記,再執行。請使用教學合成資料,不上傳真實客戶、付款、健康或兒少資料。本課是原創同概念實作,不是官方試題重製、科二全範圍總複習或通過保證。
02核心觀念:把一句話拆成可追蹤的步驟
1. 讀碼五步
先寫出輸入有幾筆、每個值的型別,再按實際執行順序追蹤變數。最後記錄輸出型別、形狀與數值,並檢查 k、平手等限制。變數名稱不同不代表物件不同;a is b 才是在問是否同一物件。
2. 預設參數在定義時建立
函式定義執行時建立的串列,會在省略該參數的呼叫間共用。append 修改原串列並回傳 None。若想每次建立新串列,使用 None 作為預設值,再在函式內建串列。
3. 近鄰投票的單位
K-nearest neighbors(KNN)分類先找距離最近的 k 筆已標記資料,再投票。這是監督式分類;K-means 的目標是分群,不使用同樣的標籤投票流程。這個一維例子只示範讀碼,不能代表真實分類成效。
03API 小卡:輸入、輸出、副作用
list.append(value)
輸入一個元素;直接修改原串列,回傳 None。不要寫 items = items.append(value)。
sorted(data, key=…)
回傳新的排序串列,不直接修改原資料;本課用距離作 key。相同距離沿用原順序,真實系統仍應訂平手規則。
Counter(labels).most_common(1)
輸入標籤序列,回傳最多一組(標籤, 次數)的串列;[0][0] 才取出標籤。本課避免票數平手。
04完整示範:先蓋住輸出再執行
把下面整段複製到一個新程式格。註解指出刻意示範的錯誤模式;其他程式含完整匯入、種子資料及斷言。assert 未報錯,代表符合本例檢查,不代表真實業務已被驗證。
from collections import Counter
def mark(order, marks=[]): # 刻意示範共享預設物件
marks.append('checked')
order['marks'] = marks
return order
first = mark({'order_id': 301})
second = mark({'order_id': 302})
print(first['marks'], second['marks'])
print('同一串列:', first['marks'] is second['marks'])
assert first['marks'] == ['checked', 'checked']
assert first['marks'] is second['marks']
data = [(2, '一般'), (5, '加急'), (9, '加急'), (14, '一般')]
query, k = 8, 3
pairs = [(abs(x - query), label) for x, label in data]
ranked = sorted(pairs, key=lambda item: item[0])
near = [label for distance, label in ranked[:k]]
prediction = Counter(near).most_common(1)[0][0]
print('距離:', [d for d, _ in ranked])
print('近鄰:', near, '預測:', prediction)
assert [d for d, _ in ranked] == [1, 3, 6, 6]
assert near == ['加急', '加急', '一般']
assert prediction == '加急'
展開實際執行輸出與逐步推導
['checked', 'checked'] ['checked', 'checked']
同一串列: True
距離: [1, 3, 6, 6]
近鄰: ['加急', '加急', '一般'] 預測: 加急
步驟 1
mark 定義時建立一個 marks 串列;第一次呼叫加入 checked,第二次在同一串列再加一次,因此先前的 first 也看到兩個標記。
步驟 2
query=8 時,四筆距離依原順序是 6、3、1、6;排序成 1、3、6、6,取前三筆。
步驟 3
前三筆標籤為加急、加急、一般,兩票對一票。本例兩個距離 6 都是一般,邊界平手不影響預測;換資料後不一定如此。
05錯誤診斷:訊息、原因、定位、修法
邏輯錯誤不一定拋例外。下段把 append 的回傳值存回 items,因此變數變成 None。先確認回傳型別,再檢查是否修改了原物件;修正後重新建立乾淨輸入。
下列區塊刻意示範失敗或錯誤語意,已在區塊內捕捉預期例外,不會中斷整本 Notebook。
items = ['start']
result = items.append('checked')
print('原串列:', items, '回傳值:', result)
assert items == ['start', 'checked'] and result is None
檢視錯誤示範的實測結果
原串列: ['start', 'checked'] 回傳值: None
06變形練習:改條件後重新推導
你的任務
請完成兩個變形。A:改寫 safe_mark(order, marks=None),即使外部傳入串列也先複製,確保不修改外部串列;兩次省略參數的呼叫各有一個 checked。B:沿用四筆近鄰資料,把 query 改成 13、k 改成 1,寫出最近距離與預測類別。
需要準備:本頁程式與可執行 Python 的 Jupyter 或 Colab。完成成果:一份預測紀錄、完整可跑的改寫程式、實際輸出及所有指定斷言。每次改一個條件,若結果不同,回到第一個分歧的步驟定位。
需要時再看提示
A 用 list(marks) 建立淺複本,本題元素只有字串;B 先計算 |x−13|,再取排序後第一個。
讀碼紀錄
輸入:筆數/欄位/特殊值
型別:主要變數與中間結果
順序:每步保留或改變了什麼
輸出:形狀/數值/類別
限制:版本/假設/平手或缺值規則
預測與實際差異:
定位與修正:
07完整參考答案與可執行斷言
完成練習後,展開答案與解析
新串列與新字典隔離了本題的外部狀態;list 與 dict 複製是淺複製,若元素還包含巢狀可變物件,仍須另外處理。query=13 時距離 1 對應 x=14,k=1 只用一般這一票。
from collections import Counter
def safe_mark(order, marks=None):
local_marks = [] if marks is None else list(marks)
local_marks.append('checked')
output = dict(order)
output['marks'] = local_marks
return output
original = ['review']
a = safe_mark({'order_id': 401})
b = safe_mark({'order_id': 402})
c = safe_mark({'order_id': 403}, original)
assert a['marks'] == b['marks'] == ['checked']
assert a['marks'] is not b['marks']
assert original == ['review']
assert c['marks'] == ['review', 'checked']
data = [(2, '一般'), (5, '加急'), (9, '加急'), (14, '一般')]
ranked = sorted([(abs(x - 13), y) for x, y in data], key=lambda p: p[0])
near = [y for _, y in ranked[:1]]
pred = Counter(near).most_common(1)[0][0]
assert ranked[0][0] == 1 and pred == '一般'
print('獨立標記:', a['marks'], b['marks'])
print('外部串列:', original)
print('最近距離:', ranked[0][0], '預測:', pred)
以下為本次實際執行輸出:
獨立標記: ['checked'] ['checked']
外部串列: ['review']
最近距離: 1 預測: 一般
08完成檢核與下一步
- □ 我能說明 first 為何在第二次呼叫後改變
- □ 我知道 append 的回傳值不是新串列
- □ 我能寫出 k=1 的距離與結果,而非只猜類別
通過標準:能說出理由、完成變形、斷言全過,並至少記錄一次原先預測與實際結果的比對;若預測正確,說明哪個中間值支持判斷。只複製答案而無推導不算完成。
下一步:讀懂缺值與型別:中位數補值及 Int64。保留本課讀碼紀錄,之後比較你最常漏掉的是資料單位、執行順序、API 回傳,還是條件邊界。
09來源、材料與驗證範圍
本課依提供的 47 頁簡報第 4–10 頁及原始 Notebook 對應觀念重新設計,資料與練習為新編。下列官方檔案查閱日為 2026-10-09;示範、錯誤檢查與答案均在 Python 本地逐段實測。完整環境版本列於課程說明與教材 README;不宣稱已在你的 Colab 或正式網站環境執行。
本頁已內嵌全部必要資料、程式、答案及結果;可下載教材只是補充,沒有附件也可完成本課。