本文 AI 產出,尚未審核

Python 函式式編程:map / filter / reduce 完全指南

簡介

在 Python 中,函式式編程(Functional Programming) 提供了一種宣告式、資料驅動的寫程式方式。相較於傳統的迴圈與條件敘述,mapfilterreduce 讓我們能夠把「什麼要做」與「怎麼做」分離,寫出更簡潔、可組合且易於測試的程式碼。

這三個高階函式在資料處理、清洗、統計分析等情境下非常常見。掌握它們的使用方式,不僅能提升程式的可讀性,還能在大量資料上獲得 較好的效能(特別是結合生成器時)。本篇文章將從概念說明、實作範例、常見陷阱到實務應用,完整帶你走進 Python 的函式式世界。


核心概念

1. map – 把函式套用到可疊代物件的每一個元素

map(function, iterable, ...) 會把 function 依序套用到 iterable(可是迭代的容器)中的每個元素,並回傳一個 map 物件(懶評估的迭代器)。
如果傳入多個 iterable,function 必須接受相同數量的參數,依序對應每個 iterable 的元素。

# 範例 1:將字串列表轉成整數
str_numbers = ['1', '2', '3', '10']
int_numbers = list(map(int, str_numbers))
print(int_numbers)          # [1, 2, 3, 10]

# 範例 2:同時使用兩個 iterable
words = ['apple', 'banana', 'cherry']
lengths = list(map(len, words))
print(lengths)              # [5, 6, 6]

# 範例 3:使用 lambda 計算座標的歐式距離
import math
points = [(1, 2), (3, 4), (5, 12)]
distances = list(map(lambda p: math.hypot(p[0], p[1]), points))
print(distances)            # [2.23606797749979, 5.0, 13.0]

小技巧:如果你只需要一次性使用結果,直接把 map 轉成 listtupleset;若要保留懶評估的特性,就保留原始的 map 物件。


2. filter – 依條件挑選出符合的元素

filter(function, iterable) 會把 function 應用到每個元素,只有回傳值為真 (True) 的元素會被保留下來,返回同樣是 filter 物件(懶評估的迭代器)。若 function 為 None,則直接過濾掉「假」值(如 0、''、None、[] 等)。

# 範例 4:過濾出正整數
numbers = [-2, -1, 0, 1, 2, 3]
positive = list(filter(lambda x: x > 0, numbers))
print(positive)             # [1, 2, 3]

# 範例 5:使用 None 直接過濾空字串
words = ['apple', '', 'banana', None, 'cherry', []]
non_empty = list(filter(None, words))
print(non_empty)            # ['apple', 'banana', 'cherry']

# 範例 6:從字串中挑出所有數字字符
s = "a1b2c3d4"
digits = ''.join(filter(str.isdigit, s))
print(digits)               # 1234

3. reduce – 把序列「累積」成單一結果

reduce(function, iterable, initializer=None) 定義於 functools 模組。它會把 function 兩兩作用於序列的元素,最終產生單一的累積值。initializer(可選)提供初始值,若未給予,第一個元素會被當作初始值。

from functools import reduce
import operator

# 範例 7:計算列表的乘積
nums = [1, 2, 3, 4, 5]
product = reduce(operator.mul, nums, 1)   # 1 為初始值
print(product)            # 120

# 範例 8:把字串列表合併成一句話
words = ['Python', '讓', '程式', '更', '簡潔']
sentence = reduce(lambda a, b: a + ' ' + b, words)
print(sentence)           # Python 讓 程式 更 簡潔

# 範例 9:找出字典列表中最大值的 key
data = [{'name': 'A', 'score': 78},
        {'name': 'B', 'score': 85},
        {'name': 'C', 'score': 92}]
max_item = reduce(lambda x, y: x if x['score'] > y['score'] else y, data)
print(max_item)           # {'name': 'C', 'score': 92}

備註:在 Python 3 之後,reduce 已不再是內建函式,而是放在 functools 中,使用時需先 import


4. 為什麼不直接用 List Comprehension?

在許多情況下,列表推導式(list comprehension) 能寫得更直觀。但 map/filter/reduce 有其獨特優勢:

用途 列表推導式 map/filter/reduce
懶評估 立即產生列表 產生迭代器,可節省記憶體
函式重用 需要自行寫迴圈 可直接傳入已有函式
多序列同時處理 較難表達 map 支援多個 iterable
可組合管線 需要手動嵌套 reduce 可把多步驟聚合成單一結果

因此在 大資料集函式抽象管線化處理 時,map/filter/reduce 仍是值得掌握的工具。


常見陷阱與最佳實踐

  1. 忘記將 map/filter 轉成序列
    直接印出 map 物件會得到 <map object at 0x...>,必須使用 list()tuple() 或在迭代時直接使用(例如 for x in map(...):)。

  2. 過度使用匿名 lambda
    雖然 lambda 讓程式碼更簡潔,但過長或過於複雜的 lambda 會降低可讀性。建議:將複雜邏輯抽成具名函式。

  3. reduce 的可讀性問題
    reduce 常被批評為「難以理解」的黑盒子。若累積邏輯超過兩三行,改用 for 迴圈或 itertools.accumulate 會更清晰。

  4. 忘記 functools.reduce 的 import
    Python 3 之後 reduce 不在內建命名空間,忘記 from functools import reduce 會直接拋出 NameError

  5. 忘記懶評估的副作用
    因為 map/filter 產生的是迭代器,若在迭代過程中改變了原始資料,結果可能出乎預期。最佳實踐:在需要固定結果時,立即轉成列表或其他容器。

  6. 使用 filter(None, iterable) 時的隱含行為
    None 會過濾掉所有「假」值,包括 0False、空字串、空列表等。若只想過濾 None,應使用 lambda x: x is not None


實際應用場景

1. 大規模資料清洗

在處理 CSV、JSON 或資料庫匯出的大量記錄時,常需要 過濾掉不合法的資料轉換欄位型別,再 聚合統計。以下示範一個簡易的 ETL(Extract‑Transform‑Load)流程:

import csv
from functools import reduce
import operator

def is_valid(row):
    # 只保留 age 欄位非空且為正整數的紀錄
    return row['age'].isdigit() and int(row['age']) > 0

def to_int_age(row):
    row['age'] = int(row['age'])
    return row

with open('users.csv', newline='', encoding='utf-8') as f:
    reader = csv.DictReader(f)
    # 1. 过滤无效记录
    valid = filter(is_valid, reader)
    # 2. 轉換 age 為 int
    transformed = map(to_int_age, valid)
    # 3. 計算平均年齡
    ages = list(map(operator.itemgetter('age'), transformed))
    avg_age = reduce(operator.add, ages, 0) / len(ages)
    print(f'平均年齡:{avg_age:.1f}')

這段程式把 filter → map → reduce 串成管線,資料只在需要時才被實體化,對記憶體友好。

2. 文字處理與自然語言前處理

在 NLP 前處理階段,常會 移除停用詞、轉小寫、做詞幹化mapfilter 能快速完成:

import re
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer

stop_words = set(stopwords.words('english'))
stemmer = PorterStemmer()

def clean_token(token):
    token = token.lower()
    token = re.sub(r'[^a-z]', '', token)   # 只保留字母
    return stemmer.stem(token)

sentence = "Python's functional programming features are powerful!"
tokens = sentence.split()
filtered = filter(lambda w: w.lower() not in stop_words, tokens)
stemmed = map(clean_token, filtered)
print(list(stemmed))   # ['python', 'function', 'program', 'power']

3. 數據分析中的累積運算

在金融或統計領域,常需要 累積收益移動平均reduce 搭配 itertools.accumulate 可寫出簡潔的累積序列:

from itertools import accumulate
import operator

daily_returns = [0.01, -0.005, 0.012, 0.003, -0.002]
# 累積報酬 = (1+r1)*(1+r2)*... - 1
cumulative = list(accumulate(daily_returns, lambda a, b: (1+a)*(1+b)-1))
print(cumulative)   # [0.01, 0.00495, 0.0179394, 0.0210952, 0.0190733]

總結

  • map 將函式映射到每個元素,適合批次轉換
  • filter 依條件篩選,讓資料流更乾淨;
  • reduce 把序列累積成單一值,常用於聚合統計

在實務開發中,適時結合 懶評估的迭代器lambda具名函式,能寫出 可讀、可維護且效能佳 的程式碼。雖然列表推導式在簡單情況下更直觀,但當面對 大型資料多序列同步處理、或 管線化需求 時,map/filter/reduce 仍是不可或缺的工具。

實務建議

  1. 先用列表推導式寫出最直觀的版本,確定邏輯正確後,再評估是否需要轉成 map/filter 以取得懶評估的好處。
  2. 避免過度嵌套 lambda,適時抽成具名函式提升可讀性。
  3. 在需要累積或統計時,先考慮 functools.reduce,若累積過程較複雜,可改用 for 迴圈或 itertools.accumulate

掌握這三大函式,你就能在 資料清洗、文字處理、統計分析 等各種場景中,寫出 簡潔、易測試且具擴充性的程式。祝你在 Python 的函式式編程之路上,玩得開心、寫得順手!