Python 串列生成式(List Comprehension)是用一行簡潔語法取代傳統 for 迴圈建立新串列的進階寫法,不僅程式碼更精簡,執行效能也優於等效的 for 迴圈。本文完整解析串列生成式的基本語法、加入條件篩選的寫法、巢狀生成式,以及延伸的字典生成式(Dict Comprehension)與集合生成式(Set Comprehension),並附上量化資料轉換與篩選的實戰範例。
什麼是串列生成式?
串列生成式(List Comprehension)是 Python 特有的語法糖,讓你用一行程式碼完成「迭代 → 轉換 → 收集」三步驟,直接產生一個新的 List,不需要先建立空 List 再逐一 append()。它的語法結構是 [運算式 for 變數 in 序列],讀起來幾乎像自然語言:「把序列中每個元素套用某個運算式,收集成一個新 List」。
可以把串列生成式想像成煉金坊的一體式精煉爐:傳統做法要先準備空容器、逐一投入原料、逐一煉製、逐一收集;生成式則把整條流水線壓縮成一道閃光,原料進去,精煉品直接出來。效能更好,程式碼也更優雅。
核心語法:從 for 迴圈到生成式
# === 對比:傳統 for 迴圈 vs 串列生成式 ===
# 傳統寫法:建立 1~10 的平方數串列(4 行)
squares = []
for x in range(1, 11):
squares.append(x ** 2)
print(squares) # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# ✅ 串列生成式(1 行,完全等效)
squares = [x ** 2 for x in range(1, 11)]
print(squares) # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# 語法結構:[運算式 for 變數 in 序列]
# ↑做什麼 ↑每次取出 ↑從哪裡取
串列生成式的語法順序是 「先寫你想要的結果,再寫從哪裡取、怎麼取」,與一般閱讀習慣相近。整個表達式包在中括號 [ ] 內,Python 看到這個結構就知道要建立一個新的 List。
# === 加入條件篩選(if 過濾)===
# 傳統寫法:取 1~20 中的偶數
evens = []
for x in range(1, 21):
if x % 2 == 0:
evens.append(x)
# ✅ 串列生成式加條件(if 寫在最後)
evens = [x for x in range(1, 21) if x % 2 == 0]
print(evens) # [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
# 語法結構:[運算式 for 變數 in 序列 if 條件]
# ↑篩選條件放最後
# 字串串列的大小寫轉換與過濾
tickers = ['AAPL', 'tsla', 'GOOGL', '', 'msft', 'NVDA']
# 取出非空字串並統一轉大寫
clean = [t.upper() for t in tickers if t]
print(clean) # ['AAPL', 'TSLA', 'GOOGL', 'MSFT', 'NVDA']
條件篩選的if放在生成式最後面(for子句之後),意思是「只有符合條件的元素才納入結果」。這與三元運算式的if-else位置不同,不要混淆——三元if-else是寫在運算式位置(最前面),用來決定元素的值;篩選if是寫在最後面,用來決定元素是否納入。
規則與注意事項
| 規則 | 正確範例 | 錯誤範例 / 陷阱 |
|---|---|---|
| 篩選 if 放最後,三元 if-else 放最前 | [x for x in lst if x > 0](篩選) |
混淆位置導致 SyntaxError 或邏輯錯誤 |
| 生成式應保持一行可讀,避免過度複雜 | 簡單轉換與篩選適合生成式 | 三層巢狀 + 複雜條件 → 改回 for 迴圈 |
| 生成式建立的是全新 List,不修改原序列 | new = [x*2 for x in old] |
誤以為會修改 old 本身 |
| 生成式內的變數不會洩漏到外部作用域 | 生成式內的 x 在外部不可見 | for 迴圈的變數則會保留在外部 |
| 需要副作用(如 print)時用 for,不用生成式 | for x in lst: print(x) |
[print(x) for x in lst](不推薦,產生無用的 None 串列) |
常見錯誤與防呆
錯誤一:混淆篩選 if 與三元 if-else 的位置
# 情境:把正數保留,負數轉成 0
# ❌ 錯誤寫法(篩選 if 放前面,語法錯誤)
nums = [3, -1, 5, -2, 8]
result = [x if x > 0 for x in nums] # SyntaxError!
# ✅ 寫法一:三元 if-else 放最前(保留所有元素,負數變 0)
result = [x if x > 0 else 0 for x in nums]
print(result) # [3, 0, 5, 0, 8]
# ✅ 寫法二:篩選 if 放最後(只保留正數,負數直接排除)
result = [x for x in nums if x > 0]
print(result) # [3, 5, 8]
錯誤二:誤用生成式產生副作用
# ❌ 不推薦寫法(用生成式呼叫 print,產生 [None, None, None])
nums = [1, 2, 3]
result = [print(x) for x in nums] # 會印出,但 result = [None, None, None]
print(result) # [None, None, None]
# ✅ 有副作用的操作用 for 迴圈
for x in nums:
print(x)
錯誤三:生成式過度巢狀,可讀性崩潰
# ❌ 過度複雜的生成式(難以閱讀與維護)
matrix = [[1,2,3],[4,5,6],[7,8,9]]
result = [cell*2 for row in matrix for cell in row if cell % 2 != 0]
# ✅ 改用 for 迴圈,加上有意義的變數名與縮排
result = []
for row in matrix:
for cell in row:
if cell % 2 != 0: # 只取奇數
result.append(cell * 2)
print(result) # [2, 6, 10, 14, 18]
進階用法
三元運算式:根據條件決定元素值
在生成式的運算式位置使用三元 if-else,可以對每個元素做「二選一的值轉換」,同時保留所有元素。
# 情境:將收盤價序列標記為漲('UP')或跌('DOWN')
closes = [100, 105, 102, 108, 99, 111]
prev = closes[0]
labels = []
# 傳統寫法
for c in closes[1:]:
labels.append('UP' if c > prev else 'DOWN')
prev = c
# ✅ 生成式(需另外處理 prev,此處用 zip 配對相鄰兩根)
labels = ['UP' if c2 > c1 else 'DOWN'
for c1, c2 in zip(closes, closes[1:])]
print(labels) # ['UP', 'DOWN', 'UP', 'DOWN', 'UP']
Dict Comprehension 與 Set Comprehension
生成式的概念可以延伸到字典(使用大括號 + 冒號)與集合(使用大括號,無冒號),語法結構完全相同,只是外層括號與格式不同。
# === Dict Comprehension:建立字典 ===
tickers = ['AAPL', 'TSLA', 'GOOGL']
prices = [185.2, 248.7, 175.9]
# 傳統做法
price_dict = {}
for t, p in zip(tickers, prices):
price_dict[t] = p
# ✅ Dict Comprehension
price_dict = {t: p for t, p in zip(tickers, prices)}
print(price_dict)
# {'AAPL': 185.2, 'TSLA': 248.7, 'GOOGL': 175.9}
# 反轉字典 Key-Value
reversed_dict = {v: k for k, v in price_dict.items()}
print(reversed_dict)
# {185.2: 'AAPL', 248.7: 'TSLA', 175.9: 'GOOGL'}
# === Set Comprehension:建立集合(自動去重)===
raw_data = [1, 2, 2, 3, 4, 4, 5]
unique_squares = {x ** 2 for x in raw_data}
print(unique_squares) # {1, 4, 9, 16, 25}(已去重)
量化實戰:收盤價資料批次處理
串列生成式在量化開發中最常用於批次轉換與篩選歷史價格資料,一行程式碼取代多行迴圈,讓程式碼更簡潔。
closes = [98.5, 102.3, 115.7, 88.2, 121.0, 95.6, 110.4, 78.9, 130.2]
# 篩選:只保留高於 100 的收盤價
above_100 = [c for c in closes if c >= 100]
print(above_100) # [102.3, 115.7, 121.0, 110.4, 130.2]
# 轉換:計算每根 K 線對第一根的漲跌幅(%)
base = closes[0]
pct_changes = [round((c - base) / base * 100, 2) for c in closes]
print(pct_changes)
# [0.0, 3.86, 17.46, -10.46, 22.84, -2.95, 12.08, -19.9, 32.18]
# 組合篩選與轉換:只取正報酬,並標記為字串
winners = [f"+{round((c-base)/base*100,1)}%"
for c in closes if c > base]
print(winners) # ['+3.9%', '+17.5%', '+22.8%', '+12.1%', '+32.2%']
煉金坊小叮嚀
串列生成式是 Python 最具代表性的優雅語法之一,我在量化開發中大量使用它來處理 K 線資料的批次轉換與篩選。但有一個原則我始終堅守:生成式只用在「一眼能看懂」的情況——邏輯簡單、條件清晰;一旦需要巢狀超過兩層或條件複雜,立刻改回 for 迴圈。可讀性永遠優先於簡潔性。另外,篩選 if 放最後、三元 if-else 放最前,這個位置規則剛開始會搞混,口訣記住:「要什麼值」寫最前,「從哪裡取」寫中間,「要不要納入」寫最後——對應三元 if-else、for、篩選 if 的排列順序。