Python – 資料結構 – 清單(list)
主題:enumerate / zip 搭配使用
簡介
在日常的 Python 程式中,我們常常需要同時取得序列的索引與元素,或是把多個序列「配對」起來進行迭代。enumerate() 與 zip() 正是為了這兩類需求而設計的內建函式。單獨使用時已相當便利,結合兩者則能讓程式碼更簡潔、可讀性更高,且避免手動管理索引或使用笨重的 for i in range(len(...)) 迴圈。
本篇文章將從概念說明、實作範例、常見陷阱到實務應用,完整介紹 enumerate 與 zip 的搭配技巧,幫助初學者快速上手,也讓中階開發者在大型專案中能寫出更具可維護性的程式。
核心概念
1. enumerate() 的基本用法
fruits = ["apple", "banana", "cherry"]
for idx, fruit in enumerate(fruits):
print(f"{idx}: {fruit}")
enumerate(iterable, start=0)會把 可疊代物件 轉成(index, element)的二元組。start參數允許自訂起始索引,常用於 1‑based 編號情境。
2. zip() 的基本用法
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 22]
for name, age in zip(names, ages):
print(f"{name} is {age} years old")
zip(*iterables)會把多個可疊代物件「平行」配對,產生 tuple 序列。- 配對的長度以最短的可疊代物件為基準,避免 IndexError。
3. 為什麼要 同時 使用 enumerate 與 zip?
在以下情境中,僅靠 enumerate 或 zip 任一個函式都無法完整表達需求:
- 同時取得索引與多個序列的元素(如同時遍歷兩個平行清單,且需要知道目前的第幾筆資料)。
- 以索引為鍵,將多個序列「合併」成字典,或是 產生矩陣的轉置。
下面的範例將示範這些實際需求,並說明如何正確、有效率地結合兩者。
程式碼範例
範例 1:同時遍歷索引與多個清單
students = ["Anna", "Ben", "Cindy"]
scores_a = [88, 92, 79]
scores_b = [90, 85, 95]
for idx, (student, a, b) in enumerate(zip(students, scores_a, scores_b), start=1):
total = a + b
print(f"{idx}. {student} – A: {a}, B: {b}, 總分: {total}")
zip(students, scores_a, scores_b)產生(student, a, b)三元組。enumerate(..., start=1)再把每筆資料編號為 1 起始。- 這樣寫比
for i in range(len(students)):更安全,不會因長度不一致而產生錯誤。
範例 2:利用 enumerate + zip 建立字典(索引 → 多欄位資料)
keys = ["id", "name", "age"]
values = [
[101, "Tom", 28],
[102, "Lucy", 24],
[103, "Mike", 31],
]
# 產生 {0: {"id":101, "name":"Tom", "age":28}, ...}
result = {
idx: dict(zip(keys, row))
for idx, row in enumerate(values)
}
print(result)
zip(keys, row)把欄位名稱與對應值配對,產生字典。enumerate(values)為每筆資料加上唯一索引(可用於資料庫主鍵或序列化)。
範例 3:交錯處理兩個清單(奇偶索引分別處理)
list1 = ["a", "b", "c", "d"]
list2 = [1, 2, 3, 4]
for idx, (ch, num) in enumerate(zip(list1, list2)):
if idx % 2 == 0: # 偶數索引
print(f"偶索引 {idx}: {ch}{num}")
else: # 奇數索引
print(f"奇索引 {idx}: {num}{ch}")
- 透過
enumerate取得 索引,再用條件分支決定不同的處理方式。 - 這在 資料清理、交錯合併(interleaving)時非常實用。
範例 4:使用 itertools.zip_longest 處理長度不等的清單
from itertools import zip_longest
names = ["Alice", "Bob"]
scores = [95, 88, 76] # 多出一筆
for idx, (name, score) in enumerate(zip_longest(names, scores, fillvalue="N/A")):
print(f"{idx}: {name} -> {score}")
zip_longest會以最長的序列為基準,缺少的元素以fillvalue填補。- 結合
enumerate後,仍能取得正確的 索引。
範例 5:矩陣轉置(list of lists → zip + enumerate)
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
# 轉置後的矩陣
transpose = [list(row) for row in zip(*matrix)]
for idx, row in enumerate(transpose):
print(f"第 {idx+1} 列: {row}")
zip(*matrix)會把每一列的相同位置元素「配對」成新列,完成 轉置。enumerate用來顯示每一列的編號,方便檢查結果。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
忘記 zip 只會配對到最短序列 |
若兩個清單長度不同,較長的部份會被「截斷」而遺失資料。 | 使用 itertools.zip_longest,或在迴圈前先檢查長度。 |
在 enumerate 中直接改變原序列 |
直接在迴圈內 list.append() 可能導致 RuntimeError(序列在迭代時改變)。 |
若需要產生新資料,先使用 list() 產生副本或使用 for idx, item in enumerate(original[:]):。 |
把 enumerate 放在 zip 內部 |
enumerate(zip(...)) 只會給 配對後的 tuple 編號,無法取得單獨的索引與元素。 |
正確順序應為 enumerate(zip(...))(可行),但若需要 索引與多個元素,建議使用 for idx, (a, b) in enumerate(zip(...)):。 |
使用 enumerate 時忘記 start 參數 |
常見於報表或 UI 顯示,需要從 1 開始編號。 | 明確寫 enumerate(iterable, start=1),避免後續手動 +1。 |
過度嵌套 zip |
多層 zip(zip(...), ...) 會產生難以閱讀的 tuple,降低可維護性。 |
盡量使用一次 zip(*iterables),或提前將資料整理成 list of tuples 再迭代。 |
最佳實踐
保持可讀性:即使可以寫成一行
for i, (a, b) in enumerate(zip(...)):,若條件較複雜,建議拆成兩行或加上註解。使用型別提示(Python 3.5+)提升 IDE 補全與靜態檢查:
from typing import List, Tuple def pair_scores(names: List[str], scores: List[int]) -> List[Tuple[int, str, int]]: return [(i, n, s) for i, (n, s) in enumerate(zip(names, scores))]盡量避免手動
range(len(...)):enumerate自帶索引,可免除越界錯誤。在大量資料處理時,若需要 記憶體友好 的迭代,請直接使用
enumerate(zip(...))(產生 generator),而非先把list(zip(...))轉成列表。
實際應用場景
| 場景 | 為什麼需要 enumerate + zip |
範例概念 |
|---|---|---|
| CSV 檔案資料清理 | 同時取得行號與多欄位值,快速定位錯誤行。 | for line_no, (id_, name, score) in enumerate(zip(col_id, col_name, col_score), start=2): |
| 圖形化繪圖(matplotlib) | 把多條曲線的座標與顏色、標籤配對,同時編號產生圖例。 | for idx, (x, y, color) in enumerate(zip(xs, ys, colors)): |
| 機器學習特徵工程 | 把特徵名稱與對應的向量索引對應起來,產生字典供模型使用。 | feature_dict = {idx: name for idx, name in enumerate(feature_names)} |
| 網路爬蟲分批請求 | 同時遍歷 URL 清單與對應的請求參數,並以序號作為日誌編號。 | for idx, (url, payload) in enumerate(zip(urls, params), start=1): |
| 多語系訊息對照表 | 把語系代碼與訊息文字配對,同時保留行號以便於翻譯工作流程。 | for idx, (lang, msg) in enumerate(zip(langs, messages), start=1): |
以上情境皆顯示,enumerate + zip 能在保持程式簡潔的同時,提供索引資訊與多序列同步迭代的能力,對於資料處理、報表產出、測試腳本等都有顯著效益。
總結
enumerate()為可疊代物件自動產生 (index, element),zip()則把多個序列「平行」配對。- 兩者結合後,可一次取得 索引、多個元素,讓程式碼避免使用
range(len(...)),減少 IndexError 與 維護成本。 - 常見的陷阱包括 長度不一致、迭代時修改原序列、以及 過度嵌套,只要遵循最佳實踐(保持可讀性、使用
start、適時使用zip_longest),即可寫出安全且易於除錯的程式。 - 在實務上,這組技巧廣泛應用於 資料清理、報表產出、圖形繪製、機器學習前處理 等領域,幾乎是每位 Python 開發者的必備功力。
掌握 enumerate 與 zip 的搭配使用,等於在 Python 的工具箱裡多了一把「雙頭螺絲刀」——既能快速定位問題,又能同時處理多筆資料,讓你的程式寫得更簡潔、可靠、易讀。祝你在日常開發中玩得開心,寫出更優雅的 Python 程式!