Python lambda 是用一行定義的匿名函數,搭配 map()(批次轉換)與 filter()(條件篩選)能讓資料處理程式碼極度精簡。本文完整解析 lambda 的語法限制與適用場景、map() 與 filter() 的運作機制與惰性求值特性、搭配 sorted() 的 key 參數用法,以及與串列生成式的取捨原則,並附量化價格資料批次處理的實戰範例。
什麼是 lambda 匿名函數?
lambda 是 Python 的匿名函數語法,用一行定義一個沒有名字的小函數,語法為 lambda 參數: 運算式。它與 def 定義的函數功能相同,差別在於 lambda 只能包含單一運算式(不能有多行邏輯或 return 語句),回傳值就是那個運算式的計算結果。
可以把 lambda 想像成一次性即用即棄的煉金小符文:不需要正式命名、不需要佔一個配方位置,只在需要的地方臨時刻上符文、用完即丟。它最常出現在需要「短暫傳入一個函數」的場合,例如 sorted() 的 key 參數、map() 與 filter() 的第一個引數。
核心語法:lambda、map 與 filter
# === lambda 基本語法 ===
# 語法:lambda 參數: 運算式
# 等同於:def func(參數): return 運算式
# 一般 def 寫法
def double(x):
return x * 2
# ✅ 等效的 lambda 寫法
double_lam = lambda x: x * 2
print(double(5)) # 10
print(double_lam(5)) # 10(完全等效)
# 多個參數
add = lambda a, b: a + b
print(add(3, 5)) # 8
# 搭配條件(三元運算式)
classify = lambda x: "正數" if x > 0 else ("負數" if x < 0 else "零")
print(classify(5)) # 正數
print(classify(-3)) # 負數
print(classify(0)) # 零
lambda 的語法結構:lambda關鍵字 → 參數(可以有多個,逗號分隔)→ 冒號:→ 單一運算式(自動回傳,不需要寫return)。lambda 只能有一個運算式,不能包含if-elif-else多行邏輯、迴圈或賦值語句。
# === map():對序列每個元素套用函數,批次轉換 ===
# 語法:map(函數, 序列)
# 回傳 map 物件(惰性迭代器),需用 list() 轉換才能看到結果
closes = [102.5, 98.3, 115.7, 88.9, 121.0]
# 傳統 for 迴圈寫法
rounded = []
for c in closes:
rounded.append(round(c))
# ✅ map() + lambda 寫法(更簡潔)
rounded = list(map(lambda x: round(x), closes))
print(rounded) # [102, 98, 116, 89, 121]
# map() + 已命名函數也可以
rounded = list(map(round, closes)) # round 本身就是函數,直接傳
print(rounded) # [102, 98, 116, 89, 121]
# 計算每根收盤價對基準的漲跌幅
base = closes[0]
pct = list(map(lambda c: round((c - base) / base * 100, 2), closes))
print(pct) # [0.0, -4.1, 12.88, -13.27, 18.05]
# map() 也可以同時處理兩個序列
buys = [100, 105, 98]
sells = [115, 102, 112]
profits = list(map(lambda b, s: s - b, buys, sells))
print(profits) # [15, -3, 14]
map()回傳的是惰性迭代器(map object),不會立即計算所有結果,只在需要時才逐一產生。若要一次取得所有結果,用list()包起來;若只需要逐一迭代,直接放在for迴圈中即可,不需要轉換成 List,可節省記憶體。
# === filter():依條件篩選序列中的元素 ===
# 語法:filter(函數, 序列)
# 函數回傳 True 的元素才會留下,False 的被過濾掉
# 同樣回傳惰性迭代器,需用 list() 轉換
closes = [102.5, 98.3, 115.7, 88.9, 121.0, 95.6, 110.4]
# 篩選高於 100 的收盤價
above_100 = list(filter(lambda c: c >= 100, closes))
print(above_100) # [102.5, 115.7, 121.0, 110.4]
# 篩選奇數
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
odds = list(filter(lambda x: x % 2 != 0, nums))
print(odds) # [1, 3, 5, 7, 9]
# 過濾空字串與 None(filter 搭配 None 作為函數,保留所有 Truthy 值)
raw = ['AAPL', '', 'TSLA', None, 'GOOGL', '', 'MSFT']
clean = list(filter(None, raw)) # None 表示「保留 Truthy 的元素」
print(clean) # ['AAPL', 'TSLA', 'GOOGL', 'MSFT']
規則與注意事項
| 規則 | 正確範例 | 錯誤範例 / 陷阱 |
|---|---|---|
| lambda 只能包含單一運算式 | lambda x: x * 2 |
lambda x: x=1; x*2 → SyntaxError |
| map / filter 回傳惰性迭代器,非 List | list(map(...)) |
直接 print(map(...)) → 印出物件地址 |
| lambda 不應取代複雜邏輯,改用 def | 簡單一行轉換用 lambda | 多行邏輯硬塞 lambda → 可讀性極差 |
| filter() 的函數必須回傳布林值(或 Truthy/Falsy) | filter(lambda x: x > 0, lst) |
函數回傳數值而非布林 → 依 Truthy 判斷,可能非預期 |
| 大多數場景 List Comprehension 比 map/filter 更 Pythonic | [x*2 for x in lst] |
過度使用 map/filter 讓程式碼反而難讀 |
常見錯誤與防呆
錯誤一:忘記用 list() 轉換 map / filter 的結果
# ❌ 錯誤寫法(直接 print map 物件,看不到內容)
result = map(lambda x: x * 2, [1, 2, 3])
print(result) #
錯誤二:lambda 內試圖寫多行邏輯
# ❌ 錯誤寫法(lambda 不支援多行或賦值語句)
# f = lambda x: if x > 0: return x else: return -x # SyntaxError
# ✅ 簡單條件用三元運算式(單行)
f = lambda x: x if x > 0 else -x
print(f(5)) # 5
print(f(-3)) # 3
# ✅ 複雜邏輯改用 def
def abs_custom(x):
if x > 0:
return x
elif x < 0:
return -x
else:
return 0
錯誤三:在迴圈中用 lambda 捕捉變數(閉包陷阱)
# ❌ 陷阱:lambda 捕捉的是變數的參考,而非當下的值
funcs = [lambda x: x + i for i in range(3)]
print(funcs[0](10)) # 12(不是 10!i 最終值是 2)
print(funcs[1](10)) # 12
print(funcs[2](10)) # 12
# ✅ 正確做法:用預設值參數固定當下的 i 值
funcs = [lambda x, i=i: x + i for i in range(3)]
print(funcs[0](10)) # 10(i=0 被固定)
print(funcs[1](10)) # 11(i=1 被固定)
print(funcs[2](10)) # 12(i=2 被固定)
進階用法
sorted() 搭配 lambda key
lambda 最常見的實用場景是作為 sorted()、min()、max() 的 key 參數,指定排序依據,讓複雜資料結構(如字典 List)也能靈活排序。
# 股票資料:依不同欄位排序
stocks = [
{'ticker': 'AAPL', 'price': 185.2, 'pe': 28.5},
{'ticker': 'TSLA', 'price': 248.7, 'pe': 65.3},
{'ticker': 'GOOGL', 'price': 175.9, 'pe': 22.1},
{'ticker': 'MSFT', 'price': 420.1, 'pe': 35.8},
]
# 依股價升冪排序
by_price = sorted(stocks, key=lambda s: s['price'])
for s in by_price:
print(f"{s['ticker']:6} ${s['price']}")
# GOOGL $175.9
# AAPL $185.2
# TSLA $248.7
# MSFT $420.1
# 依本益比降冪排序(reverse=True)
by_pe_desc = sorted(stocks, key=lambda s: s['pe'], reverse=True)
for s in by_pe_desc:
print(f"{s['ticker']:6} PE={s['pe']}")
# TSLA PE=65.3
# MSFT PE=35.8
# AAPL PE=28.5
# GOOGL PE=22.1
# min / max 搭配 key
cheapest = min(stocks, key=lambda s: s['price'])
print(f"最便宜:{cheapest['ticker']} ${cheapest['price']}") # GOOGL $175.9
map + filter 組合管道
map() 與 filter() 可以串接成資料處理管道,先篩選再轉換(或先轉換再篩選),每一層只做一件事,邏輯清晰分層。
raw_prices = [102.5, -1, 98.3, 0, 115.7, None, 88.9, 121.0]
# 步驟一:filter 過濾無效資料(負數、None、0)
valid = filter(lambda x: x is not None and x > 0, raw_prices)
# 步驟二:map 計算對第一個有效價格的漲跌幅
valid_list = list(valid)
base = valid_list[0]
pct_changes = list(map(lambda c: round((c - base) / base * 100, 2), valid_list))
print(f"有效價格:{valid_list}")
print(f"漲跌幅:{pct_changes}")
# 有效價格:[102.5, 98.3, 115.7, 88.9, 121.0]
# 漲跌幅:[0.0, -4.1, 12.88, -13.27, 18.05]
lambda 與 List Comprehension 的取捨
map()/filter() + lambda 和 List Comprehension 通常可以互相替代,Python 社群普遍偏好 List Comprehension,因為更易讀;但 map() 在處理超大資料集時因為惰性求值而更省記憶體。
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 取偶數並平方
# map + filter 寫法
result1 = list(map(lambda x: x**2, filter(lambda x: x % 2 == 0, data)))
# ✅ List Comprehension 寫法(更 Pythonic,更易讀)
result2 = [x**2 for x in data if x % 2 == 0]
print(result1) # [4, 16, 36, 64, 100]
print(result2) # [4, 16, 36, 64, 100](完全相同)
# lambda 真正的優勢場景:作為 key 參數傳入 sorted / min / max
# 此時 List Comprehension 無法替代
stocks_sorted = sorted(stocks, key=lambda s: s['pe'])
煉金坊小叮嚀
lambda、map()、filter() 是很有格調的 Python 語法,但新手容易陷入「為了用而用」的陷阱,把簡單的事情弄得複雜。我的原則很清楚:能用 List Comprehension 一行搞定的,優先用 List Comprehension,可讀性更好;lambda 留給 sorted(key=)、min(key=)、max(key=) 這類「需要臨時傳入一個函數」的場景,這才是它真正的主場。另外,map() 和 filter() 回傳惰性迭代器這件事必須記住,直接 print() 看不到內容,要用 list() 包起來或放進 for 迴圈才能取得值。最後,在迴圈中建立 lambda 時一定要留意閉包陷阱——用預設值參數 lambda x, i=i: ... 固定當下變數值,這個細節能避掉很多隱藏 bug。