Python 課程 – 控制流程 (Control Flow)
主題:for 迴圈(range、enumerate、zip)
簡介
在程式設計中,迴圈是最常用的控制結構之一,讓我們能夠對資料集合或數值範圍進行重複操作。Python 的 for 迴圈以 可疊代物件(iterable)為核心,配合內建函式 range、enumerate、zip,可以讓迴圈寫得更簡潔、更具可讀性。
掌握這三個工具,不僅能減少程式碼量,還能避免常見的索引錯誤,提升開發效率,對於 初學者 以及 中級開發者 皆是必備技能。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,逐層深入,最後提供幾個實務應用情境,幫助你在日常開發中自如運用 for 迴圈。
核心概念
1. for 迴圈的基本語法
for 變數 in 可疊代物件:
# 迴圈內要執行的程式碼
...
- 可疊代物件 可以是
list、tuple、dict、set、字串,甚至是自訂的產生器(generator)。 - 每一次迭代,
變數會被賦值為 當前元素,然後執行縮排區塊的程式碼。
小技巧:在需要同時取得索引與值時,
enumerate是最直觀的選擇。
2. range:產生整數序列
range(start, stop, step) 會返回一個 惰性序列(lazy sequence),不會一次性產生所有數字,適合大範圍的迭代。
| 參數 | 說明 |
|---|---|
start |
起始值(含),預設為 0 |
stop |
結束值(不含) |
step |
步長,正數向前、負數向後,預設為 1 |
範例 1:基本的 range 用法
# 印出 0 ~ 9
for i in range(10):
print(i, end=' ') # 0 1 2 3 4 5 6 7 8 9
範例 2:指定起始值與步長
# 印出 5, 8, 11, 14
for i in range(5, 15, 3):
print(i, end=' ') # 5 8 11 14
範例 3:倒數迴圈
# 從 10 倒數到 1
for i in range(10, 0, -1):
print(i, end=' ') # 10 9 8 7 6 5 4 3 2 1
為什麼使用
range而不是list(range(...))?range只在需要時才產生下一個數字,記憶體佔用極低;而list(range(...))會一次性把所有數字放入記憶體,對大範圍迭代會造成不必要的開銷。
3. enumerate:同時取得索引與值
enumerate(iterable, start=0) 會把可疊代物件「包裝」成 (索引, 元素) 的二元組,讓我們在迴圈裡直接取得兩者。
範例 4:遍歷列表,同時取得索引
fruits = ['蘋果', '香蕉', '葡萄', '芒果']
for idx, fruit in enumerate(fruits):
print(f'第 {idx+1} 個水果是 {fruit}')
# 第 1 個水果是 蘋果
# 第 2 個水果是 香蕉
# 第 3 個水果是 葡萄
# 第 4 個水果是 芒果
範例 5:自訂起始索引
# 從 1 開始編號
for idx, fruit in enumerate(fruits, start=1):
print(f'第 {idx} 項: {fruit}')
實務提醒:在需要「顯示」或「記錄」資料位置時,
enumerate能避免手動維護i += 1的錯誤。
4. zip:同時遍歷多個可疊代物件
zip(*iterables) 會把多個可疊代物件「配對」成元組,最短的那個序列長度決定配對數量。
範例 6:同時遍歷兩個列表
names = ['Alice', 'Bob', 'Charlie']
scores = [85, 92, 78]
for name, score in zip(names, scores):
print(f'{name} 的分數是 {score}')
# Alice 的分數是 85
# Bob 的分數是 92
# Charlie 的分數是 78
範例 7:結合 enumerate 與 zip
# 同時列出第幾筆、姓名與分數
for idx, (name, score) in enumerate(zip(names, scores), start=1):
print(f'第 {idx} 筆 - {name}: {score}')
範例 8:多重配對(三個以上)
ages = [25, 30, 22]
cities = ['台北', '新竹', '高雄']
for name, age, city in zip(names, ages, cities):
print(f'{name}({age}歲)住在{city}')
注意:如果其中一個序列長度較短,
zip只會配對到最短的長度,較長的資料會被 自動忽略。若希望保留較長的資料,可改用itertools.zip_longest。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 最佳做法 |
|---|---|---|
忘記 range 的上界不含 |
range(5) 只產生 0~4,常導致少跑一次迴圈 |
記得「上界 = 結束值 + 1」或使用 range(start, stop+1) |
| 在迴圈內改變可疊代物件 | 直接在 for 內 list.append() 或 list.remove() 會破壞迭代順序 |
先建立副本 for item in list[:],或使用 list comprehension |
enumerate 的起始值忘記設定 |
預設從 0 開始,若需求從 1 開始會產生偏差 | 明確寫 enumerate(iterable, start=1) |
zip 配對長度不符 |
長度不一的序列會自動截斷,可能遺失資料 | 若必須保留全部資料,改用 itertools.zip_longest |
使用 range 產生大量資料 |
list(range(10**9)) 會一次性占用大量記憶體 |
直接使用 range(惰性)或生成器 ((i for i in range(...))) |
進階最佳實踐
- 盡量使用內建函式:
sum(),any(),all()搭配for可寫成單行,提升可讀性。total = sum(x for x in range(1, 101)) - 避免在迴圈內做大量 I/O:如檔案寫入或網路請求,應該先收集資料,最後一次性處理,或使用非同步(
asyncio)。 - 使用列表生成式(list comprehension):當目的只是產生新列表時,
[expr for var in iterable]更簡潔。squares = [i*i for i in range(1, 11)]
實際應用場景
1. 數據清理與轉換
在資料科學專案中,常需要 遍歷 CSV 行,同時取得行號與欄位值,並將欄位轉型。enumerate 與 zip 的組合可以一次完成:
import csv
with open('sales.csv', newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row_idx, row in enumerate(reader, start=1):
# 轉換金額欄位為 float
row['金額'] = float(row['金額'])
print(f'第 {row_idx} 筆資料: {row}')
2. 同步多個序列的演算法
例如 合併兩條時間序列(股價與成交量)時,必須把相同時間點的資料配對:
times = ['09:30', '09:31', '09:32']
prices = [100, 101, 102]
volumes = [200, 210, 190]
for t, p, v in zip(times, prices, volumes):
print(f'{t} 時間點 - 價格: {p}, 成交量: {v}')
3. 產生測試資料
在單元測試中,常需要 產生多組測試案例,range 搭配 zip 可以快速產出:
inputs = [i for i in range(5)]
expected = [i*i for i in range(5)]
for inp, exp in zip(inputs, expected):
assert my_square(inp) == exp
4. 文字處理
對於 字串配對(例如雙語對照),zip 能直接把中文與英文對應:
zh = ['你好', '謝謝', '再見']
en = ['Hello', 'Thank you', 'Goodbye']
translation = dict(zip(zh, en))
print(translation['謝謝']) # Thank you
總結
for迴圈是 Python 中最直觀且功能強大的迭代工具。range提供高效的整數序列產生,適合需要索引或固定步長的情境。enumerate讓我們在遍歷時同時取得元素的索引,避免手動維護計數器。zip能同步多個可疊代物件,讓資料配對變得簡潔明瞭。
掌握這三個函式,不僅能寫出 更簡潔、可讀性更高 的程式碼,還能減少常見的索引錯誤與記憶體浪費。未來在面對資料清理、演算法實作或測試自動化時,請善用 range、enumerate、zip,讓你的 Python 程式更高效、可靠。祝你在程式之路上越走越順!