M4 容器
- 用 list 和 dict 存放資料,並用索引和鍵取出、修改其中的值;
- 沿着一層層的容器,寫出取得某個值的路徑,例如
book["author"]["name"]; - 說出 tuple 和 set 各自適合甚麼,以及它們和 JSON 的關係;
- 解釋為甚麼
b = a之後修改b,a也會跟着改變。
1容器:把很多個值放在一起 Containers
M3 的值都是單獨一個:一個價錢、一個書名。真實的資料總是一組一組的,容器就是把很多個值放在一起的值。Python 最常用的容器有四種:
| 容器 | 寫法 | 特點 | JSON 的對應 |
|---|---|---|---|
| list(串列) | ["python", "json", "yaml"] |
有次序,可以修改 | 陣列 [ ] |
| dict(字典) | {"title": "看得見的資料", "pages": 120} |
用鍵找值,可以修改 | 物件 { } |
| tuple(元組) | (22.3, 114.1) |
有次序,不能修改 | 沒有,寫出時變成陣列 |
| set(集合) | {"python", "json"} |
沒有次序,不重複 | 沒有,不能直接寫出 |
前兩種最重要:JSON 和 YAML 讀入 Python 之後,只會出現 list 和 dict。
2list:有次序的一串 Lists
list 裏的每一項都有一個位置,叫做索引(index),由 0 開始數:
tags = ["python", "json", "yaml"]
tags[0] # 'python':第一項
tags[-1] # 'yaml':負數由尾數起,-1 是最後一項
tags[1:3] # ['json', 'yaml']:由第 1 項到第 3 項之前
len(tags) # 3
"json" in tags # True
list 可以修改:
tags.append("csv") # 在尾加一項
tags[0] = "Python" # 修改第一項
索引由 0 開始,所以三項的 list 只有 tags[0]、tags[1]、tags[2]。用 tags[3] 會報 IndexError: list index out of range:索引超出範圍。
3dict:用名字找值 Dictionaries
dict 存的是一對一對的「鍵:值」。用鍵(key)來找值,不用記它排第幾:
book = {"title": "看得見的資料", "pages": 120}
book["title"] # '看得見的資料'
book["price"] = 120 # 加一個新的鍵,或者修改已有的鍵
"pages" in book # True:檢查有沒有這個鍵
list(book.keys()) # ['title', 'pages', 'price']
用一個不存在的鍵,會報 KeyError:
book["isbn"] → KeyError: 'isbn'
資料裏有些欄位不一定有(例如有些書沒有 ISBN),用 get() 比較安全:鍵不存在時,它回傳 None 或你指定的預設值,而不是報錯。
book.get("isbn") # None
book.get("isbn", "未有") # '未有'
4巢狀:JSON 的樣子 Nesting
容器裏面可以再放容器。M0 那本書讀入 Python 之後,就是一個 dict,裏面有一個 list 和另一個 dict:
這裏的 book 換成了 M0 的 book.json 讀入後的那個 dict,不是上一節那個只有 title、pages、price 的小 dict(用它寫 book["author"] 會報 KeyError)。由外而內,每進一層就加一對方括號:
book = json.loads(Path("book.json").read_text(encoding="utf-8")) # M0 的書
book["author"] # {'name': '陳大文', 'email': None}
book["author"]["name"] # '陳大文'
book["tags"][0] # 'python'
一間書店的庫存,就是一個 list,裏面每一項是一本書的 dict。這是最常見的資料形狀:
books = [
{"title": "看得見的資料", "price": 120, "stock": 3},
{"title": "給初學者的 Python", "price": 180, "stock": 0},
]
books[1]["title"] # '給初學者的 Python'
len(books) # 2:有幾本書
讀路徑的方法:books[1]["title"] 由左至右讀作「books 的第 1 項(即第二本書)的 title」。
5tuple 和 set Tuples and sets
tuple 和 list 很像,但建立後不能修改,適合放一組固定在一起的值,例如座標:
position = (22.3, 114.1)
position[0] # 22.3
position[0] = 0 # TypeError: 'tuple' object does not support item assignment
set 只記「有沒有」,自動去掉重複的值,而且沒有次序(印出來的次序每次可能不同):
set(["python", "json", "python"]) # {'json', 'python'}
{"python", "json"} | {"yaml", "json"} # 合併:{'json', 'python', 'yaml'}
JSON 沒有這兩種容器:tuple 寫成 JSON 時變成陣列,讀回來就是 list;set 不能直接寫成 JSON,要先用 list() 轉換。
選哪一種容器,可以這樣問:
flowchart LR
Q1{"要用名字找值嗎?"} -->|是| D["dict"]
Q1 -->|否| Q2{"只關心有沒有、<br/>不要重複?"}
Q2 -->|是| S["set"]
Q2 -->|否| Q3{"建立後還會修改嗎?"}
Q3 -->|會| L["list"]
Q3 -->|不會| T["tuple"]
classDef q fill:#ffffff,stroke:#d3d1cb,color:#37352f
classDef a fill:#f1f1ef,stroke:#d3d1cb,color:#37352f
class Q1,Q2,Q3 q
class D,S,L,T a
6同一個容器,兩個名字 One container, two names
M3 說過,變數只是貼在值上的名字。這件事在容器上有一個重要的後果:
a = ["python"]
b = a # b 和 a 貼在同一個 list 上
b.append("json")
a # ['python', 'json']:a 也「變」了
b = a 沒有複製出第二個 list,只是給同一個 list 多貼了一個名字。透過任何一個名字修改它,用另一個名字看到的也是修改後的樣子。
要一個獨立的副本,用 copy():
a = ["python"]
c = a.copy()
c.append("json")
a # ['python']:不受影響
copy() 只複製最外一層。如果 list 裏面放的是 dict(例如書店的 books),副本和原本仍然共用那些 dict:改副本裏某本書的價錢,原本那本也會改。練習的最後一題會讓你親眼看到。
數字、字串、tuple 都不能修改,price = price + 1 是把名字改貼到一個新的值上,所以不會影響其他名字。只有 list、dict、set 這些可以修改的容器,才需要留意「兩個名字、一個容器」。
7容易混淆 Easily confused
| 這兩樣 | 分別 |
|---|---|
| list 和 tuple | 都有次序;list 可以修改,tuple 不能。 |
d["k"] 和 d.get("k") |
鍵不存在時,前者報 KeyError,後者回傳 None(或指定的預設值)。 |
IndexError 和 KeyError |
前者是 list 的索引超出範圍;後者是 dict 裏沒有這個鍵。 |
第一項和 [1] |
索引由 0 開始,第一項是 [0],[1] 是第二項。 |
b = a 和 b = a.copy() |
前者兩個名字指向同一個容器;後者複製出一個新的容器。 |
| JSON 的陣列/物件和 Python 的 list/dict | 一一對應;JSON 沒有 tuple 和 set。 |
8動手:書店目錄 Hands-on: the bookshop catalogue
- 下載
m4-bookshop-catalog.ipynb和catalog.yaml,放進python-course資料夾; - 執行
uv run jupyter lab,打開 notebook,逐格執行。
小書店把目錄整理成一份 YAML:每本書有書名、價錢、庫存、標籤,以及一個作者的 dict。你要沿着路徑取出資料、處理有些作者沒有電郵的情況、加入一本新書、用 set 列出全店所有標籤,最後親手撞一次「兩個名字、一個容器」。
m4-sensors-eng.ipynb 和 sensors.json:三個感測器,以感測器編號為鍵的 dict,每個有一串讀數(dict of list)。把位置轉成 tuple、取出每個感測器的最新讀數,並避免在計算時改動了原始資料。
M5 講流程控制:用 for 逐項走過一個 list,用 if 按條件篩選,一次處理整間書店的資料,而不用逐本書寫一行。