第 17 課 · 進階 · 19 分鐘

Python 巢狀資料

像處理 JSON 一樣,一層一層地讀取資料。

字典列表巢狀鍵求和

訂單不是一行扁平的資料:它有明細,每條明細還有加料。想一步跳到底,KeyError 就是這麼來的。應該先檢查哪一層?

真實資料很少能塞進一個列表或字典裡。訂單包含商品,介面回應包含物件,記錄包含欄位。給每一層命名,並一次只存取一層,巢狀資料就會清楚很多。 你還會逐層驗證、把巢狀記錄攤平成行,並用顯式堆疊走訪樹。

字典組成的列表

外層列表儲存多條記錄,每個字典儲存一條記錄的欄位。走訪列表,再從每個字典裡讀取鍵。很多 JSON 檔案都是這種形狀。

orders = [
    {"drink": "latte", "price": 4.5},
    {"drink": "tea", "price": 3.0},
]
for order in orders:
    print(order["drink"], order["price"])

列表中的記錄

讀取巢狀鍵

每一層用一對方括號。data["shop"] 回傳內層字典,再用 ["name"] 讀取欄位。路徑變長時,可以先把中間結果存進變數。

data = {"shop": {"name": "Corner", "open": True}}
print(data["shop"]["name"])
print(data["shop"]["open"])

兩層鍵

更新欄位並計算總和

巢狀值仍然是普通的字典值,所以賦值方式不變。要對某個欄位求和,就走訪外層列表,從每條記錄裡讀取該鍵。能用迴圈表達意圖時,不要硬編碼索引。

orders = [{"price": 3.0}, {"price": 4.5}]
orders[0]["price"] = 3.5
total = sum(order["price"] for order in orders)
print(total)

更新並求和

每一層都要驗證

巢狀讀取是一串假設。每一層都檢查容器型別和鍵,或者先用 get() 給空預設值再索引。在邊界做一次驗證,比從三個地方分別接住 TypeError 和 KeyError 便宜。

payload = {"shop": {"name": "Corner", "orders": [{"total": 9.0}]}}
shop = payload.get("shop", {})
orders = shop.get("orders", []) if isinstance(shop, dict) else []
first_total = orders[0].get("total") if orders and isinstance(orders[0], dict) else None
print(shop.get("name", "unknown"))
print(first_total)
print(payload.get("missing", {}).get("name", "unknown"))

先檢查容器,再讀鍵

把巢狀記錄攤平成行

巢狀訂單適合儲存,卻不適合直接匯報。一次攤平一層:外層訂單、內層商品,再生成同時帶兩個標識的行。缺失值要顯式保留,不要悄悄丟掉整行。

orders = [
    {"id": 1, "items": [{"name": "latte", "qty": 2}]},
    {"id": 2, "items": [{"name": "tea", "qty": 1}, {"name": "mocha", "qty": 3}]},
]
rows = []
for order in orders:
    for item in order.get("items", []):
        rows.append((order["id"], item["name"], item.get("qty", 0)))
for row in rows:
    print(row)

每層一個迴圈,保留父級 id

用顯式堆疊走訪樹

巢狀可能很深時,顯式堆疊能避開遞迴深度限制,也讓待處理的工作看得見。想從左到右存取,就按相反順序壓入子節點;堆疊是後進先出。

tree = {"name": "menu", "children": [
    {"name": "drinks", "children": [{"name": "latte"}, {"name": "tea"}]},
    {"name": "food", "children": [{"name": "toast"}]},
]}
stack = [tree]
while stack:
    node = stack.pop()
    print(node["name"])
    stack.extend(reversed(node.get("children", [])))

堆疊儲存還沒處理的工作

本課要點

  • 巢狀資料要一層一層地用方括號讀取。
  • 字典列表是常見的記錄集合。
  • 給內層字典的鍵賦值,就能更新欄位。
  • 用迴圈或生成器表達式匯總某個欄位。
  • 巢狀資料每層都要驗證;顯式堆疊讓樹走訪更可控。

常見錯誤

  • 值還是字典時,只寫了一層方括號。
  • 不檢查記錄,就假設每條記錄都有同樣的鍵。
  • 硬編碼索引,而不是走訪記錄。
  • 子節點壓入堆疊的順序反了,卻奇怪輸出為什麼倒過來。
輪到你

建立兩個訂單字典,放進列表,然後列印總價。

動手試試

改改變數、先預測輸出,再執行程式碼。第一次點擊「執行程式碼」時會下載 Python 執行時,可能需要幾秒鐘。

瀏覽器內 Python本機執行 · 無需帳號
首次執行會從 CDN 下載 Pyodide。修改後點擊「執行程式碼」。

練習本課

12 道題,作答後立即看到解釋。

查看 12 道題答案開始練習
← 模組與匯入lambda 與 map →