M5 流程控制
for 讓同一段程式對每一項重複執行,if 讓它按條件決定做不做。兩者合起來,就能一次處理整間書店。- 用
if/elif/else按條件執行不同的程式; - 用
for走過 list 和 dict,在迴圈裏累加、計數和篩選; - 把「迴圈+條件」改寫成 comprehension,也能把 comprehension 讀回迴圈;
- 用兩層迴圈走訪巢狀資料,並改寫 M3、M4 的練習。
1為甚麼需要迴圈 Why loops
M4 的練習要列出全店的標籤,寫了這樣一行:
all_tags = (set(catalog[0]["tags"]) | set(catalog[1]["tags"]) | set(catalog[2]["tags"])
| set(catalog[3]["tags"]) | set(catalog[4]["tags"]))
它有兩個問題:書多了,這行就要一直加長;書的數目變了,這行就會出錯(少寫一本,或者寫了一本不存在的)。我們真正想說的是:「對每一本書,取出它的標籤」。這就是迴圈要做的事。
flowchart TD
S([開始]) --> N{"還有下一本書嗎?"}
N -->|有| T["book = 下一本"]
T --> C{"book 缺貨嗎?"}
C -->|是| A["記下書名"]
C -->|否| N
A --> N
N -->|沒有| E([結束])
classDef q fill:#ffffff,stroke:#d3d1cb,color:#37352f
classDef s fill:#f1f1ef,stroke:#d3d1cb,color:#37352f
class N,C q
class T,A,S,E s
2if:按條件做事 Conditions
stock = 0
if stock > 5:
print("充足")
elif stock > 0:
print("少量")
else:
print("缺貨")
Python 由上而下檢查條件,執行第一個成立的那一段,其餘的跳過。elif(else if 的縮寫)和 else 都可以不寫。
條件可以用 and、or、not 組合:
book = {"title": "看得見的資料", "price": 120, "stock": 3}
book["stock"] > 0 and book["price"] < 150 # True:有貨,而且低於 150 元
not book["stock"] > 0 # False
冒號後面縮排的那幾行,才屬於這個 if。Python 用縮排(本課一律四個空格)判斷哪些程式在條件裏面,不像其他語言用大括號。縮排不一致會報 IndentationError;縮排錯了位置則不一定報錯,只是做錯事。M0 說過 YAML 的縮排就是結構,Python 也一樣。
3for:逐項走過 Loops
先像 M4 練習那樣讀入 catalog.yaml,這一節叫它 books:一個 list,裏面有 4 本書的 dict。
books = yaml.safe_load(Path("catalog.yaml").read_text(encoding="utf-8"))
total = 0
for book in books:
total = total + book["price"] * book["stock"]
total # 1135:全店庫存的總值
for book in books: 的意思是:把 books 裏的每一項輪流交給 book 這個名字,每一輪執行一次縮排了的那段程式。第一輪 book 是第一本書,第二輪是第二本,如此類推。
迴圈前先設 total = 0,迴圈裏每一輪在原本的值上加,這是最常見的累加寫法。total = total + x 也可以簡寫成 total += x。
走過 dict 時,for key in d 拿到的是鍵;要同時拿到鍵和值,用 items()。要編號,用 enumerate()。這兩個每一輪都交出一對值,for key, value in ... 就把這一對拆開,分別交給兩個名字:
for key, value in books[0].items():
print(key, value) # title 看得見的資料 / price 120 / stock 3 / tags [...] / author {...}
for i, book in enumerate(books, start=1):
print(i, book["title"]) # 1 看得見的資料 / 2 給初學者的 Python …
4在迴圈裏篩選 Filtering in a loop
for 加 if,就能只處理符合條件的項目:
out_of_stock = []
for book in books:
if book["stock"] == 0:
out_of_stock.append(book["title"])
out_of_stock # ['給初學者的 Python']
這個「先準備一個空的 list,符合條件的就 append 進去」的模式,用得非常多,所以 Python 有一個更短的寫法。
5comprehension:一行寫完的迴圈 Comprehensions
上面那四行,可以寫成一行:
out_of_stock = [book["title"] for book in books if book["stock"] == 0]
這叫 comprehension(推導式)。它和迴圈的對應是一一的:
讀一個 comprehension 時,先讀中間的 for,再讀後面的 if,最後才讀最前面那個值:「對 books 裏每一本 book,如果缺貨,取它的 title」。
不篩選也可以,只是把每一項變換一下:
[book["price"] * 0.9 for book in books] # [108.0, 162.0, 85.5, 135.0]:九折價
{book["title"]: book["stock"] for book in books} # 書名 → 庫存的 dict
結果是一個新的 list(或 dict、set),而且一行讀得懂,就用 comprehension。要做好幾件事、要 print、要處理錯誤,或者一行太長,就用普通的迴圈。兩者效果相同,讀得懂比寫得短重要。
6走訪巢狀資料 Looping through nested data
M4 的目錄裏,每本書有一串標籤。要走到每一個標籤,就要兩層迴圈:外層走過每本書,內層走過這本書的每個標籤。
counts = {}
for book in catalog:
for tag in book["tags"]:
counts[tag] = counts.get(tag, 0) + 1
counts # {'python': 2, 'json': 2, 'yaml': 2, '入門': 2, '設定檔': 1, '圖解': 1}
counts.get(tag, 0) + 1:這個標籤之前數過就在原數上加一,第一次見到就由 0 開始。這是用 dict 計數的標準寫法。
7回看 M3、M4 的練習 Revisiting M3 and M4
M4:全店的標籤。 五個 set(...) 變成一行,書再多也不用改:
all_tags = {tag for book in catalog for tag in book["tags"]}
兩個 for 的次序和兩層迴圈一樣:外層在前,內層在後。(catalog.yaml 本身有 4 本書、6 個標籤;M4 練習裏加了一本新書,所以是 7 個。)
M3:新書總值。 原本要逐本書寫一行轉換,再人手跳過庫存未填的那本。用迴圈,規則只寫一次(M3 練習裏這份資料叫 books,這裏改叫 new_arrivals,免得和上面書店目錄的 books 混淆):
total = 0
missing = []
for book in new_arrivals:
if book["stock"] is None:
missing.append(book["title"])
continue # 跳過這一本,進入下一輪
total += float(book["price"]) * int(book["stock"])
total, missing # (927.0, ['表格與我']):和 M3 的答案一樣
continue 的意思是「這一輪到此為止,直接進入下一輪」。
8容易混淆 Easily confused
| 這兩樣 | 分別 |
|---|---|
| 迴圈和 comprehension | 效果相同;comprehension 只適合「產生一個新容器」,而且要一行讀得懂。 |
total = x 和 total += x |
前者每一輪把 total 換成新值,最後只剩最後一本書的值;後者在原本的值上加。 |
| 迴圈裏和迴圈外 | 縮排了的那幾行每一輪都執行;沒縮排的只在迴圈完結後執行一次。 |
for k in d 和 for k, v in d.items() |
前者只拿到鍵;後者同時拿到鍵和值。 |
if 和 elif |
一串 if 每一個都會檢查;if/elif 只執行第一個成立的。 |
在 for book in books: 裏面對 books 做 append 或刪除,迴圈會漏掉或重複某些項目。要篩選,就建立一個新的 list(例如用 comprehension),不要改正在走的那一個。
9動手:書店日報 Hands-on: the bookshop's daily report
- 下載
m5-daily-report.ipynb、orders.yaml,以及 M4 的catalog.yaml,放進python-course資料夾; - 執行
uv run jupyter lab,打開 notebook,逐格執行。
小書店今天收到幾張訂單,每張訂單有幾本書。你要用迴圈和條件算出每張訂單的金額、找出訂了缺貨書的訂單、統計每本書賣出多少本,最後寫成一份簡單的日報。
m5-sensor-qc-eng.ipynb:用 M4 的 sensors.json,按每種感測器的量程篩出超出範圍的讀數,計算每個感測器的平均值和最大值,並用 comprehension 和普通迴圈各寫一次。
M6 講函式與模組:把「計算一張訂單的金額」這類步驟包成函式,寫一次,到處用。