Python 教學系列:lambda、map、filter 與 reduce

Python
程式語言
搞懂 lambda、map、filter、reduce:少寫迴圈,讓 Python 資料處理更快更乾淨。
作者

Anthony

發佈於

2026年5月10日

撰寫 Python 時,或多或少有過這種經驗:明明只想把一串資料「改一改、篩一篩、加總一下」,結果迴圈越寫越長,if 越疊越深,最後自己都不想讀。這時候,lambdamapfilterreduce 通常會被拿出來,像是 Python 世界裡的瑞士刀。

多數教學會告訴你它們可以把程式碼變短。此言固然不假,卻仍偏於皮相;若見木不見林,便容易錯過其背後真正的設計脈絡。這四個工具其實對應了清楚的數學與演算法思維,因此這篇文章不只會教你怎麼寫,更會回答「為什麼這樣寫」。

lambda:匿名函式

很多人第一次覺得 lambda 好用,不是在考試題,而是在工作現場。例如 PM 說:

這份商品清單幫我按折扣後價格排序,最高的排前面。

當然可以先寫一個 def,再塞進 sorted。問題是,這段邏輯可能只會用一次,卻讓程式結構變得很重。lambda 的價值就在這裡:把一次性、小範圍、可一眼看懂的邏輯直接放回資料流程本身。

\(\lambda\)-演算1

在進入 Python 語法前,得先了解一下 lambda 的數學源頭。lambda 這個名稱來自\(\lambda\)-演算(Lambda Calculus):一套以函式為核心的形式系統。它最基本的元素只有三種:

  • 變數(variable):x
  • 抽象(abstraction):λx.M(把 x 綁定在表達式 M
  • 應用(application):(M N)(把函式 M 套用到輸入 N

最常見的規則叫 \(\beta\)-化簡(beta reduction):

\[ ((\lambda x.(x+1))(2)) \;\underset{\beta}{\mapsto}\; (2+1) \;\underset{\text{eval}}{\mapsto}\; 3 \]

也就是把 x2 代入函式本體。再看一個雙參數例子:

\[ (((\lambda x.(\lambda y.(x+y)))(2))(3)) \;\underset{\beta}{\mapsto}\; ((\lambda y.(2+y))(3)) \;\underset{\beta}{\mapsto}\; (2+3) \;\underset{\text{eval}}{\mapsto}\; 5 \]

這個概念在 Python 中也能直觀看到:

f = lambda x: (lambda y: x + y)
print(f(2)(3))  # 5
5

lambda 是什麼

講了那麼多數學,還是回到 Python——lambda 究竟是什麼?事實上,lambda 就是一種匿名函式(anonymous function)2寫法,適合把短小邏輯直接放在管線中。其基本語法為:

lambda 參數1, 參數2, ...: 回傳值

def 的關鍵差異:

  • lambda 是運算式,可以直接放在函式參數裡
  • lambda 只能寫單一運算式,不能寫多行敘述

deflambda 對照

# 一般函式
def add_one(x):
    return x + 1

# lambda 版本
add_one_lambda = lambda x: x + 1

print(add_one(5))         # 6
print(add_one_lambda(5))  # 6
6
6

若要更好理解,可以把 def 是為一個正式工具箱,lambda 則是一把口袋小刀。兩者都能切東西,但場景不同。需要重複使用、命名清晰、好維護時,def 還是首選。

lambda 實戰案例

假設有以下資料:

products = [
    {"name": "Keyboard", "price": 1290, "sales": 220},
    {"name": "Mouse", "price": 790, "sales": 430},
    {"name": "Monitor", "price": 4990, "sales": 120},
]

若要按價格排序:

by_price_desc = sorted(products, key=lambda p: p["price"], reverse=True)
print(by_price_desc)
[{'name': 'Monitor', 'price': 4990, 'sales': 120}, {'name': 'Keyboard', 'price': 1290, 'sales': 220}, {'name': 'Mouse', 'price': 790, 'sales': 430}]

若改為按預估營收(price * sales)排序:

by_revenue_desc = sorted(
    products,
    key=lambda p: p["price"] * p["sales"],
    reverse=True
)
print(by_revenue_desc)
[{'name': 'Monitor', 'price': 4990, 'sales': 120}, {'name': 'Mouse', 'price': 790, 'sales': 430}, {'name': 'Keyboard', 'price': 1290, 'sales': 220}]

這就是 lambda 最強而有力的地方,可以讓閱讀或維護程式碼的人一看就懂。

lambda 使用時機

如果出現了「把很多條件硬塞進一行」的想法在腦海中出現,通常就是該停下來的時候。例如以下的例子:

f = lambda x: x * 2 if x > 0 else (0 if x == 0 else -x + 100)

f 這個函數寫得又臭又長,且邏輯過度壓縮,可讀性很差。改成 def 可讀性會好很多:

def transform(x):
    if x > 0:
        return x * 2
    if x == 0:
        return 0
    return -x + 100

因此 lambdadef 的使用時機需視情況而定:

  • 單行、直觀、只用一次:用 lambda
  • 需要命名、重複使用、包含多步驟判斷:改用 def

程式設計不是比誰寫得最短,而是比誰在若干個月之後還看得懂自己在寫些什麼。

map:轉換函式

lambda 解決的是小函式要放哪裡,而 map 解決的是同一個轉換要怎麼套到整批資料。在資料清理、特徵工程或報表前處理中,可能會一直遇到這種需求:每一筆都做相同動作,例如去空白、轉型別、單位換算、格式統一…。

如果每次都手寫 for 迴圈,當然可以完成任務;但 map 的好處是語意更直接:直接宣告轉換規則而非控制流程。

根據 Python 官方文件map 的基本語法為:

map(function, iterable, /, *iterables, strict=False)

可以這樣理解:

  • function:為要套用的函式
  • iterable:第一個可迭代物件
  • *iterables:可傳多個可迭代物件,會平行取值套入 function
  • strict=False:預設為 False;若設為 True,多個可迭代物件的長度不一致時會拋出 ValueError

另外補充:strict 是 Python 3.14 新增參數,若使用舊版本則沒有這個參數,純屬正常現象。

map 實戰案例

假設有以下的列表:

nums = [1, 2, 3, 4, 5]

沒有學過 map 但有點 Python 基礎的的人,可能會用列表推導式(list comprehension)這樣寫:

def squared(nums: list) -> list:
    return [x ** 2 for x in nums]

print(squared(nums))
[1, 4, 9, 16, 25]

不過既然列表是一個可迭代物件,就應該善用此性質,將其丟入 map 中:

squared = map(lambda x: x ** 2, nums)
print(list(squared))  # [1, 4, 9, 16, 25]
[1, 4, 9, 16, 25]

這段其實就是一句話:把 x ** 2 這條規則,套用到 nums 裡的每個 x。如此寫法寫起來更加簡潔有力,也毋需額外定義一個可能只會用一次的函數。

map 常見陷阱

執得注意的是,map 在 Python 3 中不是直接回傳列表,而是一個迭代器,代表它通常會被消耗

nums = [1, 2, 3]
m = map(lambda x: x * 10, nums)

print(list(m))  # [10, 20, 30]
print(list(m))  # []
[10, 20, 30]
[]

第二次變空不是 bug,而是第一次已經走完。如果結果要重複使用,則需要先轉成列表後存下來

map 使用時機

當遇到以下情況時,建議使用 map

  • 對每筆資料做同一種轉換
  • 規則可抽為函式且具可重複性
  • 希望程式呈現資料管線而非控制流程

filter:篩選函式

真實資料常常不乾淨:空字串、缺漏值、測試資料混在正式資料裡。很多時候在做資料處理時的第一步不是思考如何算得更快,而是先決定哪些資料值得留下作後續分析。

根據 Python 官方文件filter 的語法是:

filter(function, iterable, /)

當我們將一個函式丟進 filter 後:

  • function 回傳 True 的元素會被保留
  • 回傳 False 的元素會被丟棄
  • filter 回傳的是迭代器

filter 實戰案例

假設有以下成績資料:

scores = [95, 71, 82, 59, 64, 48]

如果用傳統寫法,可能會這樣寫:

def pass_(lst: list) -> list:
      return [x for x in lst if x >= 60]

print(pass_(scores))
[95, 71, 82, 64]

問題仍舊與之前一樣:又多了一個可能只會用一次的函式。將 scores 丟進 filter 後,使用 lambda 加入篩選條件,就可以變得更加簡潔:

passed = filter(lambda s: s >= 60, scores)
print(list(passed))  # [95, 71, 82, 64]
[95, 71, 82, 64]

filter 小技巧

在篩選資料時,最常遇到也最需要的是將一些不必要、不需要的值剔除,以確保資料的乾淨。而 filter 有個特別的地方:若 functionNonefilter 會保留所有為真的元素:

raw = ["Alice", "", None, "Bob", 0, "Cathy", False]
cleaned = filter(None, raw)
print(list(cleaned))  # ['Alice', 'Bob', 'Cathy']
['Alice', 'Bob', 'Cathy']

雖然這在清理輸入資料時很常用,但也要注意:0False 也會被濾掉,使用時必須謹慎。

filter 常見陷阱

map 一樣,filter 也是回傳一個迭代器,因此通常只能被消耗一次:

nums = [1, 2, 3, 4, 5]
evens = filter(lambda x: x % 2 == 0, nums)

print(list(evens))  # [2, 4]
print(list(evens))  # []
[2, 4]
[]

如果後面還要重複使用,請先轉成列表存起來。

reduce:收斂函式

很多任務最後都要變成單一結果,例如總和、連乘、最大值、加權分數,或一份摘要字典。這些都屬於 reduce 的典型場景。reduce 的核心概念很簡單:維護一個累積值(accumulator),每讀到一筆新資料,就更新一次累積值,直到資料走完。

注意到,reduce 函式是在 functools 裡,而不是內建函式。根據 Python 官方文件reduce 的語法為:

reduce(function, iterable, /[, initial])
  • function(acc, x):把累積值 acc 和目前元素 x 合併後回傳新累積值
  • iterable:要收斂的無間
  • initial:初始值(建議明確給定)

reduce 實戰案例

假設有以下列表:

nums = [1, 2, 3, 4, 5]

當要加總列表中所有元素時,直觀上來說會寫一個 for 迴圈,最後回傳加總值:

sum = 0
for i in range(len(nums)):
    sum += nums[i]

print(sum)
15

但眾所週知,迴圈效率不高,能不使用迴圈就儘量避免。透過 reduce 函式搭配 lambda,可以將上述迴圈轉換為以下寫法:

from functools import reduce

total = reduce(lambda acc, x: acc + x, nums, 0)
print(total)  # 15
15

這段就等價於以下運算:

((((0 + 1) + 2) + 3) + 4) + 5

同理,如果要連乘,寫法也很簡單:

from functools import reduce

prod = reduce(lambda acc, x: acc * x, nums, 1)
print(prod)  # 120
120

initial 參數的重要性

前面提到 initial 參數雖然可以不給定,但有時若不給 initial,碰到空資料就會噴錯。例如:

from functools import reduce

nums = []

try:
    print(reduce(lambda acc, x: acc + x, nums))
except TypeError as e:
    print(e)  # reduce() of empty iterable with no initial value
reduce() of empty iterable with no initial value

因此建議在多數情境都明確提供 reduce 一個初始值。

from functools import reduce

nums = []
safe_total = reduce(lambda acc, x: acc + x, nums, 0)
print(safe_total)  # 0
0

串接範例:filter + map + reduce

既然已有如此多利器,那麼就來一次大彙總。假設有以下的列表:

nums = [-3, -1, 2, 4, 5]

我們要求將列表中的元素保留正數,再將每個元素平方,最後加總,則我們可以用以下的寫法完成上述任務:

from functools import reduce

result = reduce(
    lambda acc, x: acc + x,
    map(
        lambda x: x ** 2,
        filter(lambda x: x > 0, nums)
    ),
    0
)

print(result)  # 45
45

再給一個更貼近實際資料分析的情境,想像拿到一份原始訂單明細,裡面會混有測試單、退貨單、缺漏欄位與錯誤型別。

orders = [
    {"order_id": "A001", "status": "paid",     "is_test": False, "price": "1200", "qty": "2"},
    {"order_id": "A002", "status": "cancelled","is_test": False, "price": "900",  "qty": "1"},
    {"order_id": "A003", "status": "paid",     "is_test": True,  "price": "300",  "qty": "1"},
    {"order_id": "A004", "status": "paid",     "is_test": False, "price": None,   "qty": "4"},
    {"order_id": "A005", "status": "paid",     "is_test": False, "price": "250",  "qty": "3"},
]

我們想做的目標是:

  1. 只保留有效成交訂單(status == "paid"is_test == False
  2. 把每筆訂單轉成「營收貢獻值」(price * qty
  3. 將所有有效訂單的營收加總

一樣把這幾個工具串在一起,就可以寫出一份優雅的資料分析:

from functools import reduce

total_revenue = reduce(
    lambda acc, amount: acc + amount,
    map(
        lambda o: int(o["price"]) * int(o["qty"]),
        filter(
            lambda o: (
                o["status"] == "paid"
                and not o["is_test"]
                and o["price"] is not None
                and o["qty"] is not None
            ),
            orders
        )
    ),
    0
)

print(total_revenue)  # 3150
3150

上述流程其實可以對應到 ETL 的思路:先過濾無效資料,再做欄位轉換,最後聚合產出指標。換句話說,filter + map + reduce 不是一場語法秀,而是一個可放進實際分析管線的骨架。

練習題

問題 1

下列哪一個敘述最正確

  • A. map 會回傳列表,因此可無限次重複迭代
  • B. filtermap 在 Python 3 預設都回傳迭代器(iterator)
  • C. reduce 是 Python 的 built-in,不需額外匯入
  • D. lambda 可以寫多行 if/for 區塊

答案:B

mapfilter 在 Python 3 都回傳 iterator,通常會被消耗。reduce 位於 functools,需要 from functools import reducelambda 只能寫單一運算式。

問題 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 加總。

orders = [
    {"status": "paid", "is_test": False, "price": 100, "qty": 2},
    {"status": "cancelled", "is_test": False, "price": 300, "qty": 1},
    {"status": "paid", "is_test": True, "price": 250, "qty": 1},
    {"status": "paid", "is_test": False, "price": 80, "qty": 4},
]
from functools import reduce

total = reduce(
    lambda acc, item: acc + item["price"] * item["qty"],
    filter(lambda o: o["status"] == "paid" and not o["is_test"], orders),
    0
)

print(total)  # 520
520

這題重點在於先過濾無效資料,再用 reduce 收斂成單一指標。

問題 5

請將以下 for 迴圈改寫為 map + filter(可搭配 lambdadef):

nums = [-2, -1, 0, 1, 2, 3]
result = []
for n in nums:
    if n > 0:
        result.append(n ** 2)
print(result)
[1, 4, 9]
nums = [-2, -1, 0, 1, 2, 3]
result = list(map(lambda n: n ** 2, filter(lambda n: n > 0, nums)))
print(result)  # [1, 4, 9]
[1, 4, 9]

這題練習的是把「控制流程」轉成「資料管線」思維。

本篇小結

本篇從 lambda 出發,延伸到 mapfilterreduce,核心目標並不是追求短程式,而是希望建立一個可維護的資料處理思維。lambda 讓短邏輯可內嵌,map 負責一致轉換,filter 負責條件篩選,reduce 負責最終收斂。當這四者組合得當,就能把凌亂的資料處理程式改寫成語意清楚的管線!

表 1: lambdamapfilterreduce 用途
工具 主要任務 輸入 輸出 常見用途
lambda 定義短小匿名函式 參數 單一運算式結果 排序 key、一次性規則
map 對每筆資料做同一轉換 函式 + iterable iterator 清理字串、型別轉換、特徵轉換
filter 保留符合條件的資料 判斷函式 + iterable iterator 篩掉異常值、空值、測試資料
reduce 將多筆資料收斂為單值 累積函式 + iterable (+ initial) 單一值 加總、聚合、建立統計摘要

若能掌握先過濾、再轉換、最後收斂的概念,其實就已具備資料分析前處理最重要的基本功!

回到頂端

腳註

  1. 對數學過敏的讀者可以跳過,但如果還能夠應付數學的,仍建議閱讀並且了解背後的邏輯。↩︎

  2. 匿名函式是定義時無需指定名稱的函式,通常在只需要使用一次、簡短且不需要在其他地方呼叫的場景中,用完即丟。↩︎