1. 字典推導式概述

1.1 什麼是字典推導式?

字典推導式是 Python 提供的一種語法糖,用於在一行代碼中創建字典。它基於列表推導式(List Comprehension)的概念,允許開發者通過循環和條件邏輯快速生成鍵值對。字典推導式結合了 Python 的動態性和簡潔性,特別適合數據轉換、過濾和映射任務。

基本語法

{key_expr: value_expr for item in iterable if condition}
  • key_expr:鍵的表達式。
  • value_expr:值的表達式。
  • item:迭代變量。
  • iterable:可迭代對象(如列表、範圍)。
  • condition:可選的過濾條件。

簡單示例

# 創建一個字典,將數字映射到其平方
squares = {x: x**2 for x in range(5)}
print(squares)  # 輸出: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

1.2 字典推導式的優勢

  • 簡潔性:一行代碼替代多行循環。
  • 可讀性:清晰表達數據轉換邏輯。
  • 靈活性:支持嵌套循環、條件過濾和複雜表達式。
  • 性能:相比傳統循環,性能接近甚至更優(內部優化)。
  • 功能強大:適用於數據處理、配置生成、算法實現等。

1.3 與其他數據結構的對比

  • 列表推導式:生成列表,適合有序序列。
lst = [x**2 for x in range(5)]  # [0, 1, 4, 9, 16]
  • 集合推導式:生成無序、去重集合。
s = {x**2 for x in range(5)}  # {0, 1, 4, 9, 16}
  • 字典推導式:生成鍵值對,適合映射關係。
d = {x: x**2 for x in range(5)}  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

1.4 適用場景

  • 數據轉換:將列表、元組或其他結構轉換為字典。
  • 數據過濾:根據條件篩選鍵值對。
  • 配置管理:快速生成配置字典。
  • 算法優化:簡化映射或查找邏輯。
  • 遊戲開發:生成遊戲對象的屬性映射(如 TrafficFlowGame 的狀態表)。

1.5 相關規範

  • PEP 8:代碼風格指南,建議推導式保持簡潔、可讀。
  • PEP 274:引入字典推導式(Python 2.7+,3.0+ 完善)。
  • PEP 484:類型註解支持,提升推導式代碼可讀性。

2. 字典推導式的基本用法

2.1 簡單字典推導式

最基本的字典推導式用於生成鍵值對:

# 將名字映射到長度
names = ["Alice", "Bob", "Charlie"]
name_lengths = {name: len(name) for name in names}
print(name_lengths)  # 輸出: {'Alice': 5, 'Bob': 3, 'Charlie': 7}

説明

  • name 是迭代變量,遍歷 names
  • 鍵:name(字符串)。
  • 值:len(name)(字符串長度)。

2.2 帶條件過濾的推導式

通過 if 條件篩選鍵值對:

# 僅包含偶數的平方
even_squares = {x: x**2 for x in range(10) if x % 2 == 0}
print(even_squares)  # 輸出: {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}

説明

  • if x % 2 == 0 過濾偶數。
  • 僅滿足條件的 x 生成鍵值對。

2.3 嵌套循環推導式

支持多重循環生成鍵值對:

# 生成笛卡爾積的鍵值對
pairs = {(x, y): x + y for x in range(3) for y in range(3)}
print(pairs)  # 輸出: {(0, 0): 0, (0, 1): 1, (0, 2): 2, (1, 0): 1, ...}

説明

  • 外層循環 x,內層循環 y
  • 鍵為元組 (x, y),值為 x + y

2.4 使用現有字典

從現有字典生成新字典:

# 將值加倍
old_dict = {"a": 1, "b": 2, "c": 3}
doubled = {k: v * 2 for k, v in old_dict.items()}
print(doubled)  # 輸出: {'a': 2, 'b': 4, 'c': 6}

説明

  • items() 提供鍵值對迭代。
  • 鍵保持不變,值乘以 2。

2.5 結合函數或表達式

使用複雜表達式或函數生成鍵值對:

# 將字符串轉換為大寫並計算長度
words = ["python", "code", "data"]
upper_lengths = {word.upper(): len(word) for word in words}
print(upper_lengths)  # 輸出: {'PYTHON': 6, 'CODE': 4, 'DATA': 4}

説明

  • 鍵:word.upper()(大寫字符串)。
  • 值:len(word)(字符串長度)。

3. 高級用法與技巧

3.1 複雜條件邏輯

支持多條件過濾:

# 篩選長度大於 3 且以 'a' 開頭的名字
names = ["alice", "bob", "anna", "alexander"]
filtered = {name: len(name) for name in names if len(name) > 3 and name.startswith("a")}
print(filtered)  # 輸出: {'alice': 5, 'alexander': 9}

技巧

  • 組合 andor 實現複雜邏輯。
  • 避免條件過多,保持可讀性。

3.2 嵌套字典推導式

生成嵌套字典:

# 創建學生成績表
students = ["Alice", "Bob"]
subjects = ["Math", "Science"]
scores = {student: {subject: 0 for subject in subjects} for student in students}
print(scores)  # 輸出: {'Alice': {'Math': 0, 'Science': 0}, 'Bob': {'Math': 0, 'Science': 0}}

技巧

  • 內層推導式生成子字典。
  • 適合初始化複雜數據結構。

3.3 與 zip() 結合

使用 zip() 並行迭代多個序列:

# 配對名字和年齡
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
user_dict = {name: age for name, age in zip(names, ages)}
print(user_dict)  # 輸出: {'Alice': 25, 'Bob': 30, 'Charlie': 35}

技巧

  • zip() 適合處理等長序列。
  • 結合 dict() 構造函數:dict(zip(names, ages))

3.4 動態鍵生成

使用表達式生成鍵:

# 生成編號鍵
items = ["apple", "banana", "orange"]
indexed = {f"item_{i}": item for i, item in enumerate(items)}
print(indexed)  # 輸出: {'item_0': 'apple', 'item_1': 'banana', 'item_2': 'orange'}

技巧

  • 使用 enumerate() 添加索引。
  • 動態鍵支持字符串格式化。

3.5 條件表達式(三元運算符)

在鍵或值中使用條件表達式:

# 根據值大小分類
numbers = range(10)
categories = {n: "even" if n % 2 == 0 else "odd" for n in numbers}
print(categories)  # 輸出: {0: 'even', 1: 'odd', 2: 'even', ...}

技巧

  • 三元運算符(value if condition else other_value)簡化邏輯。
  • 避免嵌套三元運算,保持清晰。

3.6 與生成器結合

字典推導式可使用生成器表達式,節省內存:

# 處理大數據集
large_dict = {x: x**2 for x in range(1000000)}  # 直接創建
gen_dict = dict((x, x**2) for x in range(1000000))  # 生成器方式

技巧

  • 生成器表達式(圓括號)延遲計算,適合大數據。
  • 使用 sys.getsizeof() 比較內存佔用:
import sys
print(sys.getsizeof(large_dict))  # 更大
print(sys.getsizeof((x, x**2) for x in range(1000000)))  # 更小

4. 性能優化與比較

4.1 字典推導式 vs 傳統循環

字典推導式通常比等效循環更快,因其內部優化:

# 傳統循環
def create_dict_loop(n):
    d = {}
    for x in range(n):
        d[x] = x**2
    return d

# 字典推導式
def create_dict_comp(n):
    return {x: x**2 for x in range(n)}

# 性能測試
import timeit
print(timeit.timeit(lambda: create_dict_loop(1000), number=1000))  # 約 0.23 秒
print(timeit.timeit(lambda: create_dict_comp(1000), number=1000))  # 約 0.18 秒

結論

  • 推導式在 C 層優化,性能略優。
  • 對於小數據集,差異不大;大數據集建議測試。

4.2 推導式 vs dict() 構造函數

dict() 結合 zip() 相比:

names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
# 推導式
d1 = {name: age for name, age in zip(names, ages)}
# dict() + zip()
d2 = dict(zip(names, ages))
# 性能測試
print(timeit.timeit(lambda: {name: age for name, age in zip(names, ages)}, number=1000))  # 約 0.012 秒
print(timeit.timeit(lambda: dict(zip(names, ages)), number=1000))  # 約 0.010 秒

結論

  • dict(zip()) 略快,適合簡單映射。
  • 推導式更靈活,支持複雜邏輯。

4.3 內存優化

  • 大數據集:使用生成器表達式。
# 內存密集
d = {x: x**2 for x in range(1000000)}
# 內存友好
d = dict((x, x**2) for x in range(1000000))
  • 避免重複計算:緩存複雜表達式結果。
# 差:重複調用 len()
d = {name: len(name) for name in names}
# 好:緩存結果
lengths = [len(name) for name in names]
d = {name: length for name, length in zip(names, lengths)}

5. 字典推導式的實際應用場景

5.1 數據轉換與清洗

場景:從 CSV 數據提取字段。

import pandas as pd
# 假設 CSV 數據
data = pd.DataFrame({"name": ["Alice", "Bob"], "age": [25, 30]})
# 轉換為字典
name_age = {row["name"]: row["age"] for _, row in data.iterrows()}
print(name_age)  # 輸出: {'Alice': 25, 'Bob': 30}

技巧

  • 使用 to_dict() 替代推導式,效率更高:
name_age = data.set_index("name")["age"].to_dict()

5.2 配置管理

場景:生成 API 配置。

endpoints = ["users", "products", "orders"]
base_url = "https://api.example.com"
urls = {endpoint: f"{base_url}/{endpoint}" for endpoint in endpoints}
print(urls)  # 輸出: {'users': 'https://api.example.com/users', ...}

技巧

  • 使用推導式初始化默認配置。
  • 結合 defaultdict 處理缺失值。

5.3 遊戲開發(參考 TrafficFlowGame)

場景:生成紅綠燈狀態表。

lights = ["red", "green", "yellow"]
durations = [30, 60, 10]
light_config = {light: {"duration": dur, "active": False} for light, dur in zip(lights, durations)}
print(light_config)
# 輸出: {'red': {'duration': 30, 'active': False}, ...}

技巧

  • 嵌套推導式初始化複雜遊戲狀態。
  • 使用元組作為鍵表示座標:
positions = [(0, 0), (1, 1)]
states = {(x, y): "empty" for x, y in positions}

5.4 算法實現

場景:構建鄰接表(圖算法)。

edges = [(0, 1), (1, 2), (2, 0)]
graph = {node: [] for node in range(3)}
for u, v in edges:
    graph[u].append(v)
# 使用推導式初始化
graph = {node: [v for u, v in edges if u == node] for node in range(3)}
print(graph)  # 輸出: {0: [1], 1: [2], 2: [0]}

技巧

  • 推導式適合初始化圖結構。
  • 結合 collections.defaultdict(list) 簡化邏輯。

5.5 數據分析

場景:統計詞頻。

text = "apple banana apple orange"
words = text.split()
word_freq = {word: words.count(word) for word in set(words)}
print(word_freq)  # 輸出: {'apple': 2, 'banana': 1, 'orange': 1}

技巧

  • 使用 set() 去重,提高效率。
  • 結合 collections.Counter 替代 count()

6. 常見問題與解決方案

6.1 鍵重複問題

  • 問題:推導式可能覆蓋重複鍵。
pairs = [("a", 1), ("a", 2)]
d = {k: v for k, v in pairs}
print(d)  # 輸出: {'a': 2}(覆蓋)
  • 解決
  • 檢查輸入數據,確保鍵唯一。
  • 使用列表存儲多值:
d = {k: [v for _, v in pairs if _ == k] for k, _ in pairs}
print(d)  # 輸出: {'a': [1, 2]}

6.2 可讀性問題

  • 問題:複雜推導式難以閲讀。
d = {f"{x}_{y}": x * y for x in range(5) for y in range(5) if x > y and y % 2 == 0}
  • 解決
  • 分解為多行循環:
d = {}
for x in range(5):
    for y in range(5):
        if x > y and y % 2 == 0:
            d[f"{x}_{y}"] = x * y
  • 限制推導式長度(PEP 8 建議 72 字符)。

6.3 內存佔用

  • 問題:大數據集導致內存溢出。
  • 解決
  • 使用生成器表達式:
d = dict((x, x**2) for x in range(1000000))
  • 分批處理數據:
from itertools import islice
d = {x: x**2 for x in islice(range(1000000), 0, 1000)}

6.4 類型錯誤

  • 問題:鍵不可變要求。
d = {[1, 2]: "value"}  # TypeError: unhashable type: 'list'
  • 解決
  • 使用元組作為鍵:
d = {(1, 2): "value"}

7. 工具支持與工作流優化

7.1 類型註解(PEP 484)

使用類型提示提高代碼可讀性:

from typing import Dict, List
def create_mapping(names: List[str]) -> Dict[str, int]:
    return {name: len(name) for name in names}

技巧

  • 使用 Mypy 檢查類型:
pip install mypy
mypy script.py

7.2 代碼檢查工具

  • flake8:檢查推導式風格。
flake8 --max-line-length=100 script.py
  • pylint:確保推導式可讀性。
pylint --disable=too-long-line script.py

7.3 IDE 支持

  • VS Code
  • 插件:Python、Pylance(類型檢查)。
  • 自動補全:提示推導式語法。
  • PyCharm
  • 智能提示:檢測鍵類型錯誤。
  • 重構:將循環轉換為推導式。
  • Jupyter Notebook
  • 交互式測試推導式:
names = ["Alice", "Bob"]
%timeit {name: len(name) for name in names}

7.4 文檔生成

  • Sphinx:記錄推導式函數。
def create_dict(names: List[str]) -> Dict[str, int]:
    """Create a dictionary mapping names to their lengths.

    Args:
        names (List[str]): List of names.

    Returns:
        Dict[str, int]: Dictionary with name-length pairs.
    """
    return {name: len(name) for name in names}

8. 項目實踐:字典推導式的應用

8.1 數據分析

場景:處理銷售數據。

import pandas as pd
data = pd.DataFrame({"product": ["apple", "banana"], "price": [1.5, 2.0]})
price_dict = {row["product"]: row["price"] for _, row in data.iterrows()}
print(price_dict)  # 輸出: {'apple': 1.5, 'banana': 2.0}

技巧

  • 結合 to_dict() 優化:
price_dict = data.set_index("product")["price"].to_dict()

8.2 遊戲開發(參考 TrafficFlowGame)

場景:初始化交通流量狀態。

intersections = [(0, 0), (1, 1)]
traffic_states = {(x, y): {"light": "red", "vehicles": 0} for x, y in intersections}
print(traffic_states)
# 輸出: {(0, 0): {'light': 'red', 'vehicles': 0}, (1, 1): {'light': 'red', 'vehicles': 0}}

技巧

  • 使用元組鍵表示座標。
  • 嵌套推導式初始化複雜狀態。

8.3 API 數據處理

場景:解析用户數據。

import requests
users = requests.get("https://api.example.com/users").json()
user_map = {user["id"]: user["name"] for user in users}

技巧

  • 過濾有效數據:
user_map = {user["id"]: user["name"] for user in users if "name" in user}

8.4 算法優化

場景:構建倒排索引。

docs = ["apple banana", "banana orange", "apple pear"]
index = {word: [i for i, doc in enumerate(docs) if word in doc.split()] for word in set(" ".join(docs).split())}
print(index)  # 輸出: {'apple': [0, 2], 'banana': [0, 1], 'orange': [1], 'pear': [2]}

技巧

  • 使用集合去重關鍵詞。
  • 嵌套列表推導式構建值。