Python 資料結構 – 字典(dict)
主題:字典推導式(Dictionary Comprehension)
簡介
在 Python 中,字典是一種以「鍵-值」配對儲存資料的容器,常用於快速查找、關聯映射等情境。隨著程式規模的擴大,我們往往需要依據既有資料產生新的字典;若採用傳統的 for 迴圈與 dict.update(),程式碼會變得冗長且不易閱讀。
字典推導式(dictionary comprehension) 是 Python 在 2.7(與 3.x)加入的語法糖,讓我們可以在一行內完成「迭代 → 條件過濾 → 產生鍵值」的整個流程。它不僅縮短程式碼,還能提升執行效能,讓資料轉換的意圖一目了然,對 初學者 了解資料流向非常有幫助,對 中級開發者 則是撰寫乾淨、易維護程式碼的利器。
本篇將從概念說明、實作範例、常見陷阱與最佳實踐,直到實務應用場景,完整呈現字典推導式的全貌,幫助你在日常開發中快速上手並避免踩雷。
核心概念
什麼是字典推導式?
字典推導式的語法結構如下:
{ <key_expr> : <value_expr> for <item> in <iterable> if <condition> }
<key_expr>:產生鍵的表達式,可是任意可雜湊 (hashable) 的物件。<value_expr>:對應的值,可以是計算結果、函式回傳或任何 Python 表達式。for <item> in <iterable>:迭代來源,常見的有list、range()、dict.items()等。if <condition>(可選):條件過濾,只保留滿足條件的項目。
重點:字典推導式會一次性建立完整的字典,與使用
for迴圈逐筆dict[key]=value的差別在於 一次性分配記憶體,通常較為高效。
範例一:從列表產生平方表
# 產生 0~9 的平方字典:{0: 0, 1: 1, 2: 4, ...}
squares = {x: x**2 for x in range(10)}
print(squares)
說明:x 為迭代變數,x 本身作為鍵,x**2 作為值,結果是一個 鍵=原數、值=平方 的映射。
範例二:過濾奇數並轉成字串
# 只保留奇數,鍵為整數,值為字串形式
odd_str = {n: f"Number {n}" for n in range(1, 11) if n % 2 == 1}
print(odd_str)
說明:if n % 2 == 1 為條件過濾,只把奇數加入字典。f"Number {n}" 使用 f-string 產生說明文字。
範例三:反轉字典(鍵值互換)
original = {"apple": 1, "banana": 2, "cherry": 3}
# 產生新字典:{1: "apple", 2: "banana", 3: "cherry"}
reversed_dict = {v: k for k, v in original.items()}
print(reversed_dict)
說明:original.items() 會回傳 (key, value) 元組,利用解構直接交換位置,完成 鍵值翻轉。
範例四:統計字元出現次數
sentence = "Python 程式設計教學"
# 先把字元切成清單,再用推導式計算次數
char_count = {ch: sentence.count(ch) for ch in set(sentence)}
print(char_count)
說明:set(sentence) 取得不重複的字元集合,避免重複計算;sentence.count(ch) 回傳每個字元的出現次數,產生 字元頻率表。
範例五:結合兩個列表產生映射(zip + dict comprehension)
keys = ["name", "age", "city"]
values = ["Alice", 28, "Taipei"]
profile = {k: v for k, v in zip(keys, values)}
print(profile)
說明:zip() 把兩個等長的可迭代物件配對,推導式直接把配對結果寫入新字典,等同於 dict(zip(keys, values)),但更具可讀性,且可加入過濾條件。
常見陷阱與最佳實踐
| 陷阱 | 可能的問題 | 解決方式 / 最佳實踐 |
|---|---|---|
| 鍵不可雜湊 | 使用列表、字典等可變物件作為鍵會拋出 TypeError。 |
只使用不可變且可雜湊的類型(int, str, tuple 等)。 |
| 重複鍵被覆寫 | 若迭代來源產生重複鍵,後面的值會覆寫前面的值,可能不易察覺。 | 先檢查來源資料是否唯一,或使用 collections.defaultdict(list) 收集多值。 |
| 過度複雜的表達式 | 把太多邏輯塞進一行,讀者難以快速理解。 | 盡量保持單行簡潔,必要時拆成多行或使用輔助函式。 |
忘記 if 條件的括號 |
條件過濾寫錯會導致意外包含不該有的項目。 | 在條件較複雜時,使用括號明確分組,例如 if (a and b) or c:。 |
| 記憶體使用過大 | 大型資料集一次性產生完整字典會佔用大量記憶體。 | 考慮使用生成器 ((k: v for ...)) 搭配 itertools,或改用 for 迴圈逐筆寫入檔案/資料庫。 |
最佳實踐:
- 保持可讀性:即使語法允許一行寫完,也可以使用多行寫法,讓每個部份對齊,提升可讀性。
result = { k: v * 2 for k, v in source.items() if v % 2 == 0 } - 使用
dict()直接建構:當僅需簡單映射且不需要條件過濾時,dict(zip(...))仍是最直觀的寫法。 - 善用
setdefault/defaultdict:若需要同一鍵對應多個值,先用推導式產生基礎字典,再配合setdefault或defaultdict進行後續累積。 - 測試邊界:對空集合、單元素、重複鍵等情況寫單元測試,確保推導式行為符合預期。
實際應用場景
1. 讀取 CSV 檔並以欄位值作為鍵的索引
import csv
with open("employees.csv", newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
# 以 employee_id 為鍵,整筆資料為值
employee_dict = {row["employee_id"]: row for row in reader}
說明:一次性把 CSV 讀成字典,之後查詢 employee_dict["E123"] 即可得到完整紀錄,效能遠高於每次遍歷列表搜尋。
2. 快速產生 API 回傳的 JSON 轉換表
# 假設有一組外部系統的代碼與本系統的代碼對應
external_codes = ["A1", "B2", "C3"]
internal_codes = [1001, 1002, 1003]
code_map = {ext: int_code for ext, int_code in zip(external_codes, internal_codes)}
# 之後在處理 API 請求時直接使用 code_map 轉換
3. 統計網站日誌的狀態碼分布
log_lines = [
"200 OK", "404 Not Found", "200 OK",
"500 Internal Server Error", "200 OK"
]
status_counts = {code: log_lines.count(code) for code in set(log_lines)}
print(status_counts) # {'200 OK': 3, '404 Not Found': 1, '500 Internal Server Error': 1}
4. 用於資料清理:把字串型別的數字轉成真正的 int
raw_data = {"id": "00123", "age": "27", "score": "88"}
clean_data = {k: int(v) if v.isdigit() else v for k, v in raw_data.items()}
print(clean_data) # {'id': 123, 'age': 27, 'score': 88}
5. 建立多層次的巢狀字典(nested dict)快速初始化
keys = ["2023", "2024", "2025"]
nested = {year: {month: [] for month in range(1, 13)} for year in keys}
print(nested["2023"][1]) # []
總結
字典推導式是 Python 中 既簡潔又高效 的資料結構操作方式。透過單行或多行的寫法,我們可以:
- 快速產生映射(如平方表、字元頻率)
- 結合條件過濾,只保留需要的鍵值對
- 在資料清理、API 轉換、日誌統計 等實務情境中,大幅減少樣板程式碼
在使用時,務必注意 鍵的可雜湊性、避免重複鍵覆寫,以及 大型資料集的記憶體負擔。遵守最佳實踐、保持程式碼可讀性,才能讓字典推導式成為你日常開發的得力助手。
實務建議:在每個新功能或資料轉換任務開始前,先思考「是否可以用字典推導式一次完成?」若答案是「是」,那麼就把它寫進程式,讓程式碼更乾淨、效能更佳。祝你寫程式愉快,玩轉 Python!