🗺️ 程式互動式地圖
零基礎起步・第 8 課

迴圈:重複做同一件事

要對 1000 筆資料做同一件事,不必把程式抄 1000 次。這一課看懂電腦怎麼「回到前面再跑一次」,以及它怎麼知道什麼時候該停。

for、range 與 while 綠色箭頭怎麼回到迴圈開頭 break、continue、巢狀迴圈

💡 先搞懂問題

假設「晴空咖啡」有一張今天的訂單金額清單,想算出總營業額。用前幾課學到的東西,你大概會寫 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)。

生活比喻:宅配司機的一天

青松物流的宅配司機早上拿到一張配送單,上面依序列著今天要送的地址。他的工作方式很固定:看配送單上的下一筆,把車開過去、交貨、簽收,然後再看下一筆,直到配送單上的地址全部送完。他手上會夾一張「目前這一筆」的便條,每送完一戶就把便條移到下一行。

到了下午,公司換了一種說法:「車上還有貨,就繼續送。」這次沒有配送單,司機每送完一件,就要把那件貨從車上卸下來,再回頭看一眼車廂:還有貨就繼續,空了就收工。如果他忘了卸貨,車上就永遠「還有貨」,他會一直在城市裡繞下去。遇到收件人不在家,他會跳過這一戶、直接去下一戶;接到颱風停班的通知,則是立刻收工,剩下的貨明天再說。

for:照配送單一筆一筆送 while:車上還有貨就繼續 配送單 ✓ 光復路 3 號 → 民生路 18 號   中山路 2 號   和平街 9 號 目前這一筆 送完就移到下一行 最後一筆送完:結束,不必問 車上還有貨? 每一圈開始前都問 送一件 把它從車上卸下 有 回頭再問 空了 收工 忘了卸貨 → 車上永遠有貨,停不下來 continue:這戶不在家,跳過,直接去下一戶 break:颱風停班通知,剩下的不送了,立刻收工
左邊是 for:手上的便條每一圈移到配送單的下一行,送完最後一筆自動結束。右邊是 while:每一圈開始前都問一次「車上還有貨嗎?」,本體裡一定要有「卸下一件」這個讓答案會改變的動作。下方是兩種提早改變流程的方式(地址為虛構)。
回到程式:配送單就是串列 prices,for p in prices: 的意思是「從 prices 依序拿出每一個元素,每次都把名字 p 貼到那個元素上,再執行一次底下縮排的本體」。那張「目前這一筆」的便條就是迴圈變數 p:它不是新的資料,而是一張每一圈都撕下來、改貼到下一個元素上的名字標籤。while 條件: 是「車上還有貨?」,本體裡的 balance = balance + 200 這類更新就是「卸貨」,少了它條件永遠成立,就成了無窮迴圈(infinite loop)。continue 跳過這一圈剩下的部分,break 立刻結束整個迴圈。
12345678 prices = [1200, 450, 3000, 800] total = 0 count = 0 for p in prices: total = total + p if p >= 1000: count = count + 1 print("合計", total, "大單", count) 回頭 拿不到下一個 → 離開迴圈,到第 8 行 粉紅底:迴圈本體 (縮排的第 5~7 行) 綠色實線:第 7 行做完,回到第 4 行拿下一個元素 綠色虛線:第 6 行條件不成立,第 7 行跳過,同樣回到第 4 行
迴圈和第 7 課的 if 一樣靠縮排決定本體範圍,差別在本體做完之後:if 會往下走,for 則讓綠色箭頭回到第 4 行。第 4 行每執行一次,就問一次「還有下一個元素嗎?」,4 個元素就會回頭 4 次,第 5 次問的時候拿不到,才跳到第 8 行。
物件:串列 list(prices 貼在它上面) 1200 450 3000 800 0123 p ①②③④ 每一圈撕下來,改貼到下一格;④ 是最後一圈 第幾圈totalcount 開始①②③④ 01200165046505450 01122
上半部是簡化的畫法:左邊是名字、右邊是物件,p 的箭頭直接指到串列的格子(播放器裡會把 p 指著的整數另外畫成一個方塊,它和格子裡的是同一個物件)。p 不是新的格子,而是一張每圈改貼的標籤;畫面上同時只會有一條箭頭是真的(粉紅色),灰色是前幾圈貼過的位置。下半部是每一圈結束時 total 與 count 貼著的值,450 和 800 小於 1000,那兩圈 count 沒有變。

這個比喻有三個地方和程式不同。第一,司機會自己判斷「這戶好像搬家了」,程式只會照你寫的條件做,不會臨機應變。第二,司機的配送單可以邊送邊加地址,但在 for 迴圈裡邊走訪邊增刪同一個串列,會讓 Python 數錯位置、跳過元素,原理補完會示範。第三,while 的「車上還有貨嗎?」只在每一圈的開頭檢查,不是隨時在看:就算本體跑到一半條件已經不成立,這一圈還是會做完。

🎮 互動實驗室一:for 迴圈一圈一圈跑

兩段真的用 Python 執行過、逐行記錄下來的程式,可以從右上角切換:「累加與計數」把晴空咖啡今天 4 筆訂單加總,並數出 1000 元以上的大單有幾筆;「找最大值」從一週的氣溫裡找出最高的一天。按「下一步」執行一行,綠色箭頭 ➜ 是下一個要執行的行。遇到 🤔 問題時,先猜綠色箭頭會跳到第幾行,再按下一步對答案。

畫面說明:盯著兩件事。第一是程式碼左邊的綠色箭頭:本體最後一行做完,它會跳回 for 那一行,而不是往下走;直到 for 拿不到下一個元素,才跳到迴圈外的 print。第二是右邊記憶體裡的迴圈變數(p 或 t):每經過一次 for 那一行,它的箭頭就撕下來改貼到串列的下一個元素,箭頭會變成粉紅色。「找最大值」裡 best = t 執行後,best 和 t 兩條箭頭會指向同一個整數物件,這是正常的:兩張標籤貼在同一個東西上。

🎮 互動實驗室二:while、break/continue 與巢狀迴圈

右上角的選單有三段程式。「while 存到目標」每個月存 200 元,看幾個月後存款會超過 1500 元;「break 與 continue」處理一串訂單,金額 0 的跳過、遇到超過 200 的大單就停止;「巢狀迴圈 3×3」在 for 裡面再放一個 for,印出九九乘法表的一角。三段都請特別注意綠色箭頭回頭跳的位置。

畫面說明:while 的本體做完會回到第 3 行重新檢查條件,balance 從 1000 一路改貼到 1600,第 4 次檢查時 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 迴圈會跑幾圈。也可以直接按常見寫法。

寫法
■ 粉紅:產生的整數▢ 深色框:start▢ 紅虛線:stop(不含)數線範圍 -5~15


  
畫面說明:載入中。

📘 原理補完

1. for 迴圈的正式說法

Python 官方教學說,for 會「依照元素在序列中出現的順序,逐一走訪任何序列(例如串列或字串)的元素」。能被 for 一個一個取出元素的東西,統稱可迭代物件(iterable):串列、字串、range、字典(第 6 課學過,取出的是鍵)都是。寫法是 for 名字 in 可迭代物件:,冒號下一行開始縮排的部分是本體。電腦執行的步驟是:

  1. 執行 for 那一行:向可迭代物件要「下一個元素」。
  2. 拿得到:把迴圈變數(for 後面的名字)撕下來改貼到這個元素上,執行一次本體,本體最後一行做完就回到步驟 1。
  3. 拿不到(全部走過了):迴圈結束,跳到本體後面第一行沒有縮排的程式。

迴圈結束之後,迴圈變數還在,貼著的是最後一個元素:實驗室一跑完時,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 一一對應。

012345678910 range(5)range(2, 6)range(1, 10, 3)range(10, 0, -2) 01234 2345 147 108642 不含不含不含不含 step 是負數:從 10 往左倒數,每次減 2
粉紅色是 range 產生的整數,紅色虛線框是 stop,永遠不會出現在結果裡。第三列每次跳 3 格,10 是終點所以停在 7;第四列 step 是 -2,從 10 往回數到 2。可以到實驗室三自己調整。
寫法產生的整數(實際執行)個數常見用途
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 點
points = 50 points < 200 ? True points += 30 visits += 1 False 印出結果 points += 30 是 points = points + 30 的簡寫 每次檢查時的 points ① 50 < 200 True② 80 < 200 True③ 110 < 200 True④ 140 < 200 True⑤ 170 < 200 True⑥ 200 < 200 False 如果少了 points += 30 points 永遠是 50,條件永遠 True, 本體一圈又一圈跑下去(無窮迴圈), 只能按 Ctrl+C 強制中斷
菱形是 while 那一行的條件,粉紅虛線是「本體做完,回頭再檢查」。右邊列出 6 次檢查:前 5 次成立、跑了 5 圈,第 6 次 200 < 200 不成立才離開,所以 visits 是 5。

忘了更新條件時,程式不會報錯,只會一直跑。在終端機裡按 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 是「整個迴圈到此結束,跳到迴圈後面」。巢狀迴圈裡,它們只影響自己所在的那一層迴圈。

continue:跳過這一圈 break:結束整個迴圈 1234512345 for x in orders: if x == 0: continue print(x) print("結束") for x in orders: if x > 200: break print(x) print("結束") 回到第 1 行拿下一個元素, 這一圈的第 4 行不執行 直接跳到迴圈外的第 5 行, orders 裡剩下的元素都不看了
兩段程式只差一個關鍵字,綠色箭頭的方向卻完全相反:continue 往上回到 for,break 往下離開迴圈。實驗室二的「break 與 continue」把兩者放在同一段程式裡,可以逐步看到兩種跳法。

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 行後面接一行 ---
j = 1j = 2j = 3 i = 1i = 2i = 3 第1次第2次第3次第4次第5次第6次第7次第8次第9次 123246369 內層 j 跑完一輪→ 印 ---→ 外層 i 加 1j 重新從 1 開始 粉紅箭頭:內層 j 往右走;灰色虛線:換下一列(外層換下一個 i)
格子裡是 i * j,左上角的「第幾次」是內層本體執行的順序:先把第一列由左到右走完,才換到第二列。實驗室二的「巢狀迴圈 3×3」可以一步一步看 i、j 兩張標籤怎麼改貼。

7. 陷阱:不要一邊走訪、一邊增刪同一個串列

for 走訪串列時,內部其實是記著「目前走到第幾格」。如果在本體裡用第 5 課的 remove 或 append 改變同一個串列,格子會整排移位,for 卻照樣往下一格走,結果就會漏看或多看元素。下面想刪掉所有的 2,實際跑出來卻還留著一個:

第 1 圈第 2 圈第 3 圈 1223 1223 123 0123012 看第 0 格:1,不是 2,略過 看第 1 格:2 → remove(2) 後面的 2、3 整排往前移一格 看第 2 格:3 原本的第二個 2 移到第 1 格,被跳過了 print(nums) → [1, 2, 3],不是想要的 [1, 3]
粉紅色格子是 for 這一圈拿到的位置(for 內部記著「第幾格」)。第 2 圈刪掉一個元素後,整排往前移,第 3 圈的「第 2 格」已經是 3 了。
安全的寫法:不要改正在走訪的串列,改成建立一個新串列,把要留下的元素 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