撰寫 Python 時,或多或少有過這種經驗:明明只想把一串資料「改一改、篩一篩、加總一下」,結果迴圈越寫越長,if 越疊越深,最後自己都不想讀。這時候,lambda、map、filter、reduce 通常會被拿出來,像是 Python 世界裡的瑞士刀。
多數教學會告訴你它們可以把程式碼變短。此言固然不假,卻仍偏於皮相;若見木不見林,便容易錯過其背後真正的設計脈絡。這四個工具其實對應了清楚的數學與演算法思維,因此這篇文章不只會教你怎麼寫,更會回答「為什麼這樣寫」。
lambda:匿名函式
很多人第一次覺得 lambda 好用,不是在考試題,而是在工作現場。例如 PM 說:
這份商品清單幫我按折扣後價格排序,最高的排前面。
當然可以先寫一個 def,再塞進 sorted。問題是,這段邏輯可能只會用一次,卻讓程式結構變得很重。lambda 的價值就在這裡:把一次性、小範圍、可一眼看懂的邏輯直接放回資料流程本身。
$\lambda$-演算skip
在進入 Python 語法前,得先了解一下 lambda 的數學源頭。lambda 這個名稱來自$\lambda$-演算(Lambda Calculus):一套以函式為核心的形式系統。它最基本的元素只有三種:
- 變數(variable):
x - 抽象(abstraction):
λx.M(把x綁定在表達式M) - 應用(application):
(M N)(把函式M套用到輸入N)
最常見的規則叫 $\beta$-化簡(beta reduction):
也就是把 x 用 2 代入函式本體。再看一個雙參數例子:
這個概念在 Python 中也能直觀看到:
1 | f = lambda x: (lambda y: x + y) |
lambda 是什麼
講了那麼多數學,還是回到 Python——lambda 究竟是什麼?事實上,lambda 就是一種匿名函式(anonymous function)anonymous寫法,適合把短小邏輯直接放在管線中。其基本語法為:
1 | lambda 參數1, 參數2, ...: 回傳值 |
和 def 的關鍵差異:
lambda是運算式,可以直接放在函式參數裡lambda只能寫單一運算式,不能寫多行敘述
def 與 lambda 對照
1 | # 一般函式 |
若要更好理解,可以把 def 是為一個正式工具箱,lambda 則是一把口袋小刀。兩者都能切東西,但場景不同。需要重複使用、命名清晰、好維護時,def 還是首選。
lambda 實戰案例
假設有以下資料:
1 | products = [ |
若要按價格排序:
1 | by_price_desc = sorted(products, key=lambda p: p["price"], reverse=True) |
若改為按預估營收(price * sales)排序:
1 | by_revenue_desc = sorted( |
這就是 lambda 最強而有力的地方,可以讓閱讀或維護程式碼的人一看就懂。
lambda 使用時機
如果出現了「把很多條件硬塞進一行」的想法在腦海中出現,通常就是該停下來的時候。例如以下的例子:
1 | f = lambda x: x * 2 if x > 0 else (0 if x == 0 else -x + 100) |
f 這個函數寫得又臭又長,且邏輯過度壓縮,可讀性很差。改成 def 可讀性會好很多:
1 | def transform(x): |
因此 lambda 與 def 的使用時機需視情況而定:
- 單行、直觀、只用一次:用
lambda - 需要命名、重複使用、包含多步驟判斷:改用
def
程式設計不是比誰寫得最短,而是比誰在若干個月之後還看得懂自己在寫些什麼。
map:轉換函式
lambda 解決的是小函式要放哪裡,而 map 解決的是同一個轉換要怎麼套到整批資料。在資料清理、特徵工程或報表前處理中,可能會一直遇到這種需求:每一筆都做相同動作,例如去空白、轉型別、單位換算、格式統一…。
如果每次都手寫 for 迴圈,當然可以完成任務;但 map 的好處是語意更直接:直接宣告轉換規則而非控制流程。
根據 Python 官方文件,map 的基本語法為:
1 | map(function, iterable, /, *iterables, strict=False) |
可以這樣理解:
function:為要套用的函式iterable:第一個可迭代物件*iterables:可傳多個可迭代物件,會平行取值套入functionstrict=False:預設為False;若設為True,多個可迭代物件的長度不一致時會拋出ValueError
另外補充:strict 是 Python 3.14 新增參數,若使用舊版本則沒有這個參數,純屬正常現象。
map 實戰案例
假設有以下的列表:
1 | nums = [1, 2, 3, 4, 5] |
沒有學過 map 但有點 Python 基礎的的人,可能會用列表推導式(list comprehension)這樣寫:
1 | def squared(nums: list) -> list: |
不過既然列表是一個可迭代物件,就應該善用此性質,將其丟入 map 中:
1 | squared = map(lambda x: x ** 2, nums) |
這段其實就是一句話:把 x ** 2 這條規則,套用到 nums 裡的每個 x。如此寫法寫起來更加簡潔有力,也毋需額外定義一個可能只會用一次的函數。
map 常見陷阱
執得注意的是,map 在 Python 3 中不是直接回傳列表,而是一個迭代器,代表它通常會被消耗:
1 | nums = [1, 2, 3] |
第二次變空不是 bug,而是第一次已經走完。如果結果要重複使用,則需要先轉成列表後存下來。
map 使用時機
當遇到以下情況時,建議使用 map:
- 對每筆資料做同一種轉換
- 規則可抽為函式且具可重複性
- 希望程式呈現資料管線而非控制流程
filter:篩選函式
真實資料常常不乾淨:空字串、缺漏值、測試資料混在正式資料裡。很多時候在做資料處理時的第一步不是思考如何算得更快,而是先決定哪些資料值得留下作後續分析。
根據 Python 官方文件,filter 的語法是:
1 | filter(function, iterable, /) |
當我們將一個函式丟進 filter 後:
function回傳True的元素會被保留- 回傳
False的元素會被丟棄 filter回傳的是迭代器
filter 實戰案例
假設有以下成績資料:
1 | scores = [95, 71, 82, 59, 64, 48] |
如果用傳統寫法,可能會這樣寫:
1 | def pass_(lst: list) -> list: |
問題仍舊與之前一樣:又多了一個可能只會用一次的函式。將 scores 丟進 filter 後,使用 lambda 加入篩選條件,就可以變得更加簡潔:
1 | passed = filter(lambda s: s >= 60, scores) |
filter 小技巧
在篩選資料時,最常遇到也最需要的是將一些不必要、不需要的值剔除,以確保資料的乾淨。而 filter 有個特別的地方:若 function 傳 None,filter 會保留所有為真的元素:
1 | raw = ["Alice", "", None, "Bob", 0, "Cathy", False] |
雖然這在清理輸入資料時很常用,但也要注意:0 與 False 也會被濾掉,使用時必須謹慎。
filter 常見陷阱
和 map 一樣,filter 也是回傳一個迭代器,因此通常只能被消耗一次:
1 | nums = [1, 2, 3, 4, 5] |
如果後面還要重複使用,請先轉成列表存起來。
reduce:收斂函式
很多任務最後都要變成單一結果,例如總和、連乘、最大值、加權分數,或一份摘要字典。這些都屬於 reduce 的典型場景。reduce 的核心概念很簡單:維護一個累積值(accumulator),每讀到一筆新資料,就更新一次累積值,直到資料走完。
注意到,reduce 函式是在 functools 裡,而不是內建函式。根據 Python 官方文件,reduce 的語法為:
1 | reduce(function, iterable, /[, initial]) |
function(acc, x):把累積值acc和目前元素x合併後回傳新累積值iterable:要收斂的無間initial:初始值(建議明確給定)
reduce 實戰案例
假設有以下列表:
1 | nums = [1, 2, 3, 4, 5] |
當要加總列表中所有元素時,直觀上來說會寫一個 for 迴圈,最後回傳加總值:
1 | sum = 0 |
但眾所週知,迴圈效率不高,能不使用迴圈就儘量避免。透過 reduce 函式搭配 lambda,可以將上述迴圈轉換為以下寫法:
1 | from functools import reduce |
這段就等價於以下運算:
1 | ((((0 + 1) + 2) + 3) + 4) + 5 |
同理,如果要連乘,寫法也很簡單:
1 | from functools import reduce |
initial 參數的重要性
前面提到 initial 參數雖然可以不給定,但有時若不給 initial,碰到空資料就會噴錯。例如:
1 | from functools import reduce |
因此建議在多數情境都明確提供 reduce 一個初始值。
1 | from functools import reduce |
串接範例:filter + map + reduce
既然已有如此多利器,那麼就來一次大彙總。假設有以下的列表:
1 | nums = [-3, -1, 2, 4, 5] |
我們要求將列表中的元素保留正數,再將每個元素平方,最後加總,則我們可以用以下的寫法完成上述任務:
1 | from functools import reduce |
再給一個更貼近實際資料分析的情境,想像拿到一份原始訂單明細,裡面會混有測試單、退貨單、缺漏欄位與錯誤型別。
1 | orders = [ |
我們想做的目標是:
- 只保留有效成交訂單(
status == "paid"且is_test == False) - 把每筆訂單轉成「營收貢獻值」(
price * qty) - 將所有有效訂單的營收加總
一樣把這幾個工具串在一起,就可以寫出一份優雅的資料分析:
1 | from functools import reduce |
上述流程其實可以對應到 ETL 的思路:先過濾無效資料,再做欄位轉換,最後聚合產出指標。換句話說,filter + map + reduce 不是一場語法秀,而是一個可放進實際分析管線的骨架。
練習題
問題 1
下列哪一個敘述最正確?
- A.
map會回傳列表,因此可無限次重複迭代 - B.
filter與map在 Python 3 預設都回傳迭代器(iterator) - C.
reduce是 Python 的 built-in,不需額外匯入 - D.
lambda可以寫多行if/for區塊
答案:B
map 與 filter 在 Python 3 都回傳 iterator,通常會被消耗。reduce 位於 functools,需要 from functools import reduce。lambda 只能寫單一運算式。
問題 2
給定 a = [1, 2, 3] 與 b = [10, 20],執行 list(map(lambda x, y: x + y, a, b)) 的結果為何?
- A.
[11, 22, 3] - B. 拋出
ValueError - C.
[11, 22] - D.
[11, 22, None]
答案:C
多個 iterable 傳給 map 時會平行取值,預設行為是以最短序列為準,因此只會產出兩筆。
問題 3
下列哪個寫法最適合「空列表也要安全加總」的情境?
- A.
reduce(lambda acc, x: acc + x, nums, 0) - B.
reduce(lambda acc, x: acc + x, nums) - C.
sum = reduce(lambda acc, x: acc + x, nums) - D.
reduce(sum, nums)
答案:A
提供 initial=0 可以避免空列表時拋出 TypeError,也是實務上最穩定的寫法。
問題 4
請完成下列任務:
給定 orders,先保留 status == "paid" 且 is_test == False 的資料,再計算總營收 price * qty 加總。
1 | orders = [ |
1 | from functools import reduce |
這題重點在於先過濾無效資料,再用 reduce 收斂成單一指標。
問題 5
請將以下 for 迴圈改寫為 map + filter(可搭配 lambda 或 def):
1 | nums = [-2, -1, 0, 1, 2, 3] |
1 | nums = [-2, -1, 0, 1, 2, 3] |
這題練習的是把「控制流程」轉成「資料管線」思維。
本篇小結
本篇從 lambda 出發,延伸到 map、filter 與 reduce,核心目標並不是追求短程式,而是希望建立一個可維護的資料處理思維。lambda 讓短邏輯可內嵌,map 負責一致轉換,filter 負責條件篩選,reduce 負責最終收斂。當這四者組合得當,就能把凌亂的資料處理程式改寫成語意清楚的管線!
| 工具 | 主要任務 | 輸入 | 輸出 | 常見用途 |
|---|---|---|---|---|
lambda |
定義短小匿名函式 | 參數 | 單一運算式結果 | 排序 key、一次性規則 |
map |
對每筆資料做同一轉換 | 函式 + iterable | iterator | 清理字串、型別轉換、特徵轉換 |
filter |
保留符合條件的資料 | 判斷函式 + iterable | iterator | 篩掉異常值、空值、測試資料 |
reduce |
將多筆資料收斂為單值 | 累積函式 + iterable (+ initial) | 單一值 | 加總、聚合、建立統計摘要 |
`lambda`、`map`、`filter`、`reduce` 用途
若能掌握先過濾、再轉換、最後收斂的概念,其實就已具備資料分析前處理最重要的基本功!
skip. 對數學過敏的讀者可以跳過,但如果還能夠應付數學的,仍建議閱讀並且了解背後的邏輯。 ↩
anonymous. 匿名函式是定義時無需指定名稱的函式,通常在只需要使用一次、簡短且不需要在其他地方呼叫的場景中,用完即丟。 ↩