💡 先搞懂問題
假設「晴空咖啡」有一張今天的訂單金額清單,想算出總營業額。用前幾課學到的東西,你大概會寫 total = orders[0] + orders[1] + orders[2] + …。清單有 4 筆時還好,有 400 筆就寫不完了;更麻煩的是,明天的訂單數量不一樣,程式也得跟著改。我們真正想說的是:「清單裡的每一筆,都做同一件事」,筆數由資料決定,程式只寫一次。
這就是迴圈(loop):讓電腦把同一段程式重複執行很多次。第一次讀迴圈的人常有三個疑問:第一,程式不是由上往下執行嗎?怎麼會「回到前面」?第二,for p in prices: 裡的 p 是什麼,為什麼每一圈都不一樣?第三,電腦怎麼知道什麼時候該停,會不會停不下來?這一課用逐行執行播放器把這三件事看清楚:綠色箭頭跑到區塊最後一行之後,會跳回 for 或 while 那一行,而不是往下走。
Python 有兩種迴圈。for 迴圈從一串資料(例如第 5 課的串列,一排有編號的格子)裡依序拿出每一個元素,拿完就結束,跑幾圈由資料的長度決定。while 迴圈則是「條件成立就再跑一圈」,每一圈開始前都檢查一次第 7 課學過的條件,條件不成立才停。迴圈底下縮排的那幾行叫迴圈本體(loop body),每跑一次本體叫做一圈(一次迭代,iteration)。
生活比喻:宅配司機的一天
青松物流的宅配司機早上拿到一張配送單,上面依序列著今天要送的地址。他的工作方式很固定:看配送單上的下一筆,把車開過去、交貨、簽收,然後再看下一筆,直到配送單上的地址全部送完。他手上會夾一張「目前這一筆」的便條,每送完一戶就把便條移到下一行。
到了下午,公司換了一種說法:「車上還有貨,就繼續送。」這次沒有配送單,司機每送完一件,就要把那件貨從車上卸下來,再回頭看一眼車廂:還有貨就繼續,空了就收工。如果他忘了卸貨,車上就永遠「還有貨」,他會一直在城市裡繞下去。遇到收件人不在家,他會跳過這一戶、直接去下一戶;接到颱風停班的通知,則是立刻收工,剩下的貨明天再說。
prices,for p in prices: 的意思是「從 prices 依序拿出每一個元素,每次都把名字 p 貼到那個元素上,再執行一次底下縮排的本體」。那張「目前這一筆」的便條就是迴圈變數 p:它不是新的資料,而是一張每一圈都撕下來、改貼到下一個元素上的名字標籤。while 條件: 是「車上還有貨?」,本體裡的 balance = balance + 200 這類更新就是「卸貨」,少了它條件永遠成立,就成了無窮迴圈(infinite loop)。continue 跳過這一圈剩下的部分,break 立刻結束整個迴圈。
這個比喻有三個地方和程式不同。第一,司機會自己判斷「這戶好像搬家了」,程式只會照你寫的條件做,不會臨機應變。第二,司機的配送單可以邊送邊加地址,但在 for 迴圈裡邊走訪邊增刪同一個串列,會讓 Python 數錯位置、跳過元素,原理補完會示範。第三,while 的「車上還有貨嗎?」只在每一圈的開頭檢查,不是隨時在看:就算本體跑到一半條件已經不成立,這一圈還是會做完。
🎮 互動實驗室一:for 迴圈一圈一圈跑
兩段真的用 Python 執行過、逐行記錄下來的程式,可以從右上角切換:「累加與計數」把晴空咖啡今天 4 筆訂單加總,並數出 1000 元以上的大單有幾筆;「找最大值」從一週的氣溫裡找出最高的一天。按「下一步」執行一行,綠色箭頭 ➜ 是下一個要執行的行。遇到 🤔 問題時,先猜綠色箭頭會跳到第幾行,再按下一步對答案。
best = t 執行後,best 和 t 兩條箭頭會指向同一個整數物件,這是正常的:兩張標籤貼在同一個東西上。🎮 互動實驗室二:while、break/continue 與巢狀迴圈
右上角的選單有三段程式。「while 存到目標」每個月存 200 元,看幾個月後存款會超過 1500 元;「break 與 continue」處理一串訂單,金額 0 的跳過、遇到超過 200 的大單就停止;「巢狀迴圈 3×3」在 for 裡面再放一個 for,印出九九乘法表的一角。三段都請特別注意綠色箭頭回頭跳的位置。
1600 < 1500 不成立才離開。break 與 continue 那一段,輸出畫面最後是「大單 250」和「結束」,清單最後的 60 根本沒被拿出來。巢狀迴圈那一段,內層的 j 每次都會跑完 1、2、3,外層的 i 才換下一個,所以總共印出 9 行算式;內層跑完時,綠色箭頭先到第 4 行印出分隔線,再回到第 1 行。🎮 互動實驗室三:range 探索器
for i in range(...) 是最常見的「跑固定次數」寫法。先選 range 的寫法,再拖曳滑桿調整 start(起點)、stop(終點)、step(間隔),上方的數線會標出產生了哪些整數:粉紅色是被產生的數,深色框是起點,紅色虛線框是終點(不會被產生)。下方顯示對應的 Python 與實際輸出,len 就是 for 迴圈會跑幾圈。也可以直接按常見寫法。
📘 原理補完
1. for 迴圈的正式說法
Python 官方教學說,for 會「依照元素在序列中出現的順序,逐一走訪任何序列(例如串列或字串)的元素」。能被 for 一個一個取出元素的東西,統稱可迭代物件(iterable):串列、字串、range、字典(第 6 課學過,取出的是鍵)都是。寫法是 for 名字 in 可迭代物件:,冒號下一行開始縮排的部分是本體。電腦執行的步驟是:
- 執行 for 那一行:向可迭代物件要「下一個元素」。
- 拿得到:把迴圈變數(for 後面的名字)撕下來改貼到這個元素上,執行一次本體,本體最後一行做完就回到步驟 1。
- 拿不到(全部走過了):迴圈結束,跳到本體後面第一行沒有縮排的程式。
迴圈結束之後,迴圈變數還在,貼著的是最後一個元素:實驗室一跑完時,p 仍然指向 800。另外要注意,for 後面接的必須是可迭代物件,寫成 for i in 5: 會出現 TypeError,想跑 5 次要寫 for i in range(5):。
2. range:產生一串整數,不含結尾
range 會產生一串等距的整數,最常拿來控制迴圈跑幾圈。它有三種寫法:range(stop) 從 0 開始;range(start, stop) 從 start 開始;range(start, stop, step) 每次加 step,step 可以是負數,用來倒數。三種寫法都包含起點、不包含終點,所以 range(5) 是 0 到 4,剛好 5 個數,和串列的索引 0 到 4 一一對應。
| 寫法 | 產生的整數(實際執行) | 個數 | 常見用途 |
|---|---|---|---|
range(5) | [0, 1, 2, 3, 4] | 5 | 「重複 5 次」;也剛好是長度 5 的串列的所有索引 |
range(2, 6) | [2, 3, 4, 5] | 4 | 從某個數數到另一個數(不含) |
range(1, 10, 3) | [1, 4, 7] | 3 | 每隔幾個取一個 |
range(10, 0, -2) | [10, 8, 6, 4, 2] | 5 | 倒數 |
range(5, 2) | [] | 0 | 起點已經超過終點:一個都不產生,迴圈一圈都不跑,也不會報錯 |
直接 print(range(5)) 只會印出 range(0, 5),因為 range 不會事先把所有數字存起來,而是要用時才一個一個算出來;想看清楚內容就寫 list(range(5))。想要「1 到 10」時記得寫 range(1, 11):寫成 range(1, 10) 程式不會報錯,只是加總會得到 45 而不是 55,這種少一圈的錯誤非常常見。
3. 迴圈裡最常見的四種寫法
讀資料分析程式時,迴圈本體裡出現最多的是這四種模式,看到時認出來就好:累加(先寫 total = 0,每一圈加上新的值);計數(先寫 count = 0,符合條件時加 1);篩選(for 裡面放第 7 課的 if,只處理符合條件的元素,常搭配第 5 課的 append 收集到新串列);找最大值(先拿一個起點,每一圈「比目前最大的還大就換人」)。累加器和計數器一定要放在迴圈外面先建立,放進本體裡的話,每一圈都會被重新貼回 0。
| 比較項目 | for 迴圈 | while 迴圈 |
|---|---|---|
| 什麼時候停 | 可迭代物件裡的元素拿完了 | 每一圈開始前檢查的條件變成 False |
| 跑幾圈 | 事先就知道(元素個數、range 的長度) | 事先不一定知道,由資料或計算結果決定 |
| 需要自己更新什麼 | 不用,for 會自動拿下一個元素 | 本體裡一定要更新條件用到的名字 |
| 忘記更新會怎樣 | 不會發生 | 條件永遠成立,變成無窮迴圈 |
| 典型用途 | 處理清單裡的每一筆、重複固定次數 | 存到目標為止、重試到成功、讀到沒有資料為止 |
4. while:條件成立就繼續,一定要更新條件
while 條件: 的規則很單純:每一圈開始前算一次條件,True 就執行本體,本體做完回到 while 那一行再算一次;False 就跳到本體後面。整件事成立的前提是:本體裡有某一行會讓條件「往 False 的方向」改變。下面這段會員點數的例子,points = points + 30 就是那一行。
points = 50
visits = 0
while points < 200: # 每一圈開始前檢查:還不夠換
points = points + 30 # 更新條件會用到的名字
visits = visits + 1
print("再來", visits, "次,就有", points, "點")
# 輸出:再來 5 次,就有 200 點
忘了更新條件時,程式不會報錯,只會一直跑。在終端機裡按 Ctrl+C 可以強制中斷,Python 會印出 KeyboardInterrupt,停下來的行號取決於你按下的那一刻程式跑到哪裡。下面是一段忘記更新 count 的程式被中斷時的實際畫面(前面已經印出了成千上萬行「還在跑」):
count = 0 / while count < 3: / print("還在跑") → 按下 Ctrl+CTraceback (most recent call last): File "main.py", line 3, in <module> print("還在跑") ~~~~~^^^^^^^^^^ KeyboardInterrupt
在 Jupyter Notebook 或 Colab 裡,對應的做法是按工具列的「中斷」或停止鈕。
5. break 與 continue:提早改變流程
這兩個關鍵字只能寫在迴圈的本體裡,for 和 while 都能用,通常放在某個 if 底下。continue 是「這一圈剩下的不做了,直接回到迴圈開頭進行下一圈」;break 是「整個迴圈到此結束,跳到迴圈後面」。巢狀迴圈裡,它們只影響自己所在的那一層迴圈。
6. 巢狀迴圈:for 裡面再放一個 for
迴圈的本體裡可以再放一個迴圈,叫做巢狀迴圈(nested loop)。規則只有一條:外層每跑一圈,內層就從頭完整跑一次。所以外層 3 圈、內層 3 圈,內層的本體一共會執行 3 × 3 = 9 次。表格、圖片(一列一列、一格一格)的處理常會用到這種寫法。
for i in range(1, 4): # 外層:i 是 1、2、3
for j in range(1, 4): # 內層:每個 i 都把 j 從 1 跑到 3
print(i, "x", j, "=", i * j)
print("---") # 縮排 4 格:屬於外層,每個 i 印一次
# 輸出 9 行算式,每 3 行後面接一行 ---
i * j,左上角的「第幾次」是內層本體執行的順序:先把第一列由左到右走完,才換到第二列。實驗室二的「巢狀迴圈 3×3」可以一步一步看 i、j 兩張標籤怎麼改貼。7. 陷阱:不要一邊走訪、一邊增刪同一個串列
for 走訪串列時,內部其實是記著「目前走到第幾格」。如果在本體裡用第 5 課的 remove 或 append 改變同一個串列,格子會整排移位,for 卻照樣往下一格走,結果就會漏看或多看元素。下面想刪掉所有的 2,實際跑出來卻還留著一個:
append 進去。這樣原本的 nums 一格都不會動,for 也就不會數錯。nums = [1, 2, 2, 3]
for n in nums:
if n == 2:
nums.remove(n) # ✗ 一邊走訪一邊刪除
print(nums) # [1, 2, 3]:第二個 2 被跳過了
nums = [1, 2, 2, 3]
keep = [] # ✓ 準備一個新的串列
for n in nums:
if n != 2:
keep.append(n) # 只把要留下的放進去
print(keep) # [1, 3]
8. 常見錯誤與錯誤訊息的長相
和第 7 課一樣,冒號與縮排寫錯是語法錯誤,程式一行都不會執行;其他錯誤則是跑到那一行才發生。以下都是 Python 3.13 實際執行的畫面(File 後面原本是完整路徑,這裡簡寫成 main.py):
① for 底下的本體沒有縮排 File "main.py", line 2 print(p) ^^^^^ IndentationError: expected an indented block after 'for' statement on line 1
② for 那一行忘了冒號 File "main.py", line 1 for p in [1, 2] ^ SyntaxError: expected ':'
③ 想跑 5 次,卻把數字直接放在 in 後面Traceback (most recent call last): File "main.py", line 1, in <module> for i in 5: ^ TypeError: 'int' object is not iterable
④ range 的 step 寫成 0Traceback (most recent call last): File "main.py", line 1, in <module> print(list(range(0, 10, 0))) ~~~~~^^^^^^^^^^ ValueError: range() arg 3 must not be zero
⑤ range 只接受整數Traceback (most recent call last): File "main.py", line 1, in <module> print(list(range(0, 1, 0.5))) ~~~~~^^^^^^^^^^^ TypeError: 'float' object cannot be interpreted as an integer
最難發現的反而是沒有錯誤訊息的情況:range 少算一圈、累加器放進迴圈本體裡、while 少了更新。遇到「答案怪怪的」時,用本頁的播放器那樣一步一步追,或在迴圈本體裡加一行 print 看每一圈的值,第 10 課會整理成除錯步驟。
9. 兩段完整範例
第一段把 for 的四種常見寫法放在一起:用 continue 排除取消的訂單,再累加、計數、找最大值,迴圈結束後計算平均。
# 晴空咖啡今天的訂單金額(示意),0 代表被取消的訂單
orders = [120, 0, 85, 300, 0, 45]
total = 0 # 累加器:從 0 開始往上加
count = 0 # 計數器:有效訂單有幾筆
biggest = 0 # 目前看過最大的一筆(金額不會是負的,才能從 0 開始)
for amount in orders: # 依序拿出每一筆,amount 每一圈改貼
if amount == 0:
continue # 取消的訂單:這一圈剩下的部分不做
total = total + amount # 累加
count = count + 1 # 計數
if amount > biggest: # 找最大值:比目前最大的還大才換
biggest = amount
print("有效訂單", count, "筆,合計", total, "元") # 迴圈外:只印一次
print("最大一筆", biggest, "元")
print("平均", total / count, "元")
# 輸出:
# 有效訂單 4 筆,合計 550 元
# 最大一筆 300 元
# 平均 137.5 元
第二段用 while 搭配索引(第 5 課的 sales[month])找出第一個達標的月份,找到就用 break 停下來。注意 month = month + 1 寫在 if 後面:沒找到才往下一格移動。
sales = [80, 95, 130, 70, 150] # 每月營業額(萬元,示意)
month = 0 # 目前看到第幾格(索引從 0 開始)
while month < len(sales): # 還沒看完所有月份就繼續
if sales[month] >= 120:
print("第一次破 120 萬是第", month + 1, "個月")
break # 找到了就停,後面的月份不看
month = month + 1 # 更新索引;少了這行會一直看同一個月
print("檢查完畢")
# 輸出:
# 第一次破 120 萬是第 3 個月
# 檢查完畢
以後讀 AI 或資料分析的範例程式,迴圈會以這些樣子出現:訓練模型時的 for epoch in range(10):(把整份資料看 10 遍),內層再一批一批讀資料的巢狀迴圈;處理文件時「每一個檔案、每一行文字」做同樣的清理;呼叫網路服務失敗時,用 while 重試到成功或達到次數上限為止。第 9 課會把迴圈本體包成函式,讓同一段步驟更容易重複使用。
✅ 自我檢測
6 題原創的程式閱讀題,每一題的答案都用 Python 3.13 實際執行確認過。選完立即顯示對錯與解析,全部作答後會出現總分。目前得分:0 / 6