Python 的 map、filter、reduce 怎么用?和列表推导式比哪个好?
简化版
**map、filter、reduce 是函数式编程的三个基本工具:map(f, it) 把函数 f 作用到每个元素(映射/转换)、filter(f, it) 保留 f 返回真的元素(过滤/筛选)、reduce(f, it) 用二元函数把序列「折叠」成一个值(归约/累积)。**要点:① map/filter 返回惰性迭代器(Python 3,不是列表,只能遍历一次、省内存),要列表得 list(map(...));② reduce 在 functools 里(Python 3 从内置移到 functools,因为 Guido 认为它可读性差),reduce(lambda a,b: a+b, [1,2,3,4]) = ((1+2)+3)+4 = 10,可带初始值 reduce(f, it, init);③ 和列表推导式比——map/filter 大多数场景不如推导式直观([f(x) for x in it]、[x for x in it if cond]),Python 社区更推荐推导式;但 map(内置函数, it)(如 map(str, nums))时 map 更简洁、且惰性省内存。reduce 的替代:求和用 sum、最大用 max、连接用 "".join、累积用 itertools.accumulate——这些内置比 reduce 更清晰。核心记忆:map 映射/filter 过滤/reduce 归约;map/filter 返回惰性迭代器,多数场景用列表推导式更 Pythonic;reduce 在 functools 里、能用 sum/max/join 就别用 reduce。
详细版
三个函数对比:
| 函数 | 作用 | 返回 | 推荐替代 |
|---|---|---|---|
map(f, it) | 每个元素应用 f | 惰性迭代器 | [f(x) for x in it] |
filter(f, it) | 保留 f 为真的 | 惰性迭代器 | [x for x in it if f(x)] |
reduce(f, it) | 折叠成一个值 | 单个值 | sum/max/join/accumulate |
from functools import reduce
nums = [1, 2, 3, 4, 5]
# map:映射(每个元素转换)
list(map(lambda x: x**2, nums)) # [1, 4, 9, 16, 25]
list(map(str, nums)) # ['1','2','3','4','5'](内置函数时简洁)
# map 多个可迭代:并行取
list(map(lambda a,b: a+b, [1,2], [10,20])) # [11, 22]
# filter:过滤(保留真的)
list(filter(lambda x: x % 2 == 0, nums)) # [2, 4]
list(filter(None, [0, 1, "", "a", None])) # [1, 'a'](None=保留真值)
# reduce:归约(折叠成一个值)
reduce(lambda a, b: a + b, nums) # 15(((((1+2)+3)+4)+5))
reduce(lambda a, b: a * b, nums) # 120(连乘)
reduce(lambda a, b: a + b, nums, 100) # 115(带初始值 100)
reduce(max, nums) # 5(找最大)
# 惰性:map/filter 是迭代器(只遍历一次)
m = map(str, nums)
list(m) # ['1',...]
list(m) # [](耗尽了!)
# vs 列表推导式(多数场景更推荐)
[x**2 for x in nums] # 比 map(lambda) 直观
[x for x in nums if x % 2 == 0] # 比 filter(lambda) 直观
# reduce 的更好替代
sum(nums) # 15(别用 reduce 求和)
max(nums) # 5
"".join(["a", "b", "c"]) # 'abc'(别用 reduce 拼字符串)
⚠️ 实用结论:
map/filter/reduce是函数式思想的经典三件套,但在 Python 里它们大多数场景「不如列表推导式和内置函数清晰」——Python 是「多范式但偏命令式/推导式」的语言,社区共识是优先用推导式。具体建议:① 转换用推导式[f(x) for x in it]比map(lambda x: f(x), it)直观,但当 f 是「现成的函数/内置函数」时map(str, nums)、map(int, strs)反而更简洁(不用写 lambda);② 筛选用推导式[x for x in it if cond]比filter(lambda...)清晰;③reduce尽量避免——Guido 当年就想删掉它(可读性差、要在脑子里模拟折叠过程),能用sum(求和)、max/min(极值)、"".join(拼接)、math.prod(连乘,3.8+)、itertools.accumulate(累积中间结果)这些「专门的、有名字的」内置就别用 reduce。真正适合 reduce 的是「没有现成内置的、自定义的折叠逻辑」(如按自定义规则合并字典)。关键记住map/filter返回惰性迭代器(省内存、只能遍历一次),这既是优点(大数据)也是坑(想复用要先list())。
完整版教学
一、map:映射
先理解 map:
map(func, iterable, ...):把 func 作用到每个元素
→ 返回一个"每个元素都被 func 转换后"的迭代器
单个可迭代:
map(f, [a, b, c]) → 迭代产出 f(a), f(b), f(c)
list(map(lambda x: x*2, [1,2,3])) → [2, 4, 6]
多个可迭代(并行取):
map(f, [1,2,3], [10,20,30])
→ f(1,10), f(2,20), f(3,30)
→ 以最短的为准(多余的丢弃,同 zip)
返回惰性迭代器(Python 3):
map(...) 不立即计算,边遍历边算
→ 省内存(大数据友好)、只能遍历一次
什么时候 map 比推导式好:
✓ func 是现成函数/内置(不用写 lambda):
map(str, nums) # 比 [str(x) for x in nums] 略简洁
map(int, str_list)
map(len, list_of_lists)
✗ func 要写 lambda:
map(lambda x: x*2, nums) # 不如 [x*2 for x in nums]
配合 * 解包:
# 转置或批量应用
list(map(list, zip(*matrix)))
所以 map(f,it)每个元素应用 f 返回惰性迭代器;func 是现成函数时比推导式简洁
map(func, iterable, ...) 把 func 作用到每个元素——返回一个每个元素都被 func 转换后的迭代器。单个可迭代:map(f, [a,b,c]) 产出 f(a), f(b), f(c)。多个可迭代(并行取):map(f, [1,2,3], [10,20,30]) 产出 f(1,10), f(2,20), f(3,30)(以最短为准、同 zip)。返回惰性迭代器(Python 3):不立即计算、边遍历边算(省内存、只能遍历一次)。什么时候 map 比推导式好:func 是现成函数/内置时(map(str, nums) 比 [str(x) for x in nums] 略简洁),func 要写 lambda 时不如推导式(map(lambda x: x*2, nums) 不如 [x*2 for x in nums])。理解「map(f,it)每个元素应用 f 返回惰性迭代器;多可迭代并行取(以最短为准);func 是现成函数(str/int/len)时比推导式简洁、要写 lambda 不如推导式」,就掌握了 map。
二、filter:过滤
理解 filter:
filter(func, iterable):保留 func 返回真的元素
→ 返回"只含满足条件元素"的迭代器
filter(lambda x: x > 0, [-1, 2, -3, 4]) → 2, 4
→ func(x) 为真则保留,为假则丢弃
func 为 None 的特殊用法:
filter(None, iterable) → 保留所有"真值"元素
filter(None, [0, 1, "", "a", None, [], [5]])
→ [1, 'a', [5]](去掉所有假值:0, "", None, [])
→ 相当于"去除假值"的快捷方式
返回惰性迭代器(同 map):
filter(...) 惰性、只遍历一次
vs 列表推导式:
filter(lambda x: cond(x), it) ≈ [x for x in it if cond(x)]
→ 推导式通常更直观(尤其条件复杂时)
filter(None, it) ≈ [x for x in it if x]
filter + map 组合(先筛后转):
# 偶数的平方
list(map(lambda x: x**2, filter(lambda x: x%2==0, nums)))
# 推导式更清晰:
[x**2 for x in nums if x % 2 == 0]
itertools 里的相关:
itertools.filterfalse(f, it) # 保留 f 为假的(filter 的反面)
itertools.compress(data, selectors) # 按布尔选择器过滤
所以 filter(f,it)保留 f 为真的元素返回惰性迭代器;filter(None,it)去假值;推导式更直观
filter(func, iterable) 保留 func 返回真的元素——返回只含满足条件元素的迭代器(filter(lambda x: x > 0, [-1,2,-3,4]) → 2, 4)。func 为 None 的特殊用法:filter(None, iterable) 保留所有真值元素(filter(None, [0,1,"","a",None,[],[5]]) → [1,'a',[5]],去掉所有假值,相当于「去除假值」的快捷方式)。返回惰性迭代器(同 map)。vs 列表推导式:filter(lambda x: cond(x), it) ≈ [x for x in it if cond(x)](推导式通常更直观、尤其条件复杂时)。filter + map 组合(先筛后转):推导式 [x**2 for x in nums if x % 2 == 0] 比嵌套的 map/filter 清晰。itertools 里的相关:filterfalse(保留 f 为假的)、compress(按布尔选择器过滤)。理解「filter(f,it)保留 f 为真的返回惰性迭代器;filter(None,it)去假值(快捷);推导式更直观;filter+map 组合不如推导式;itertools.filterfalse/compress」,就掌握了 filter。
三、reduce:归约
理解 reduce 的折叠过程:
reduce(func, iterable[, initializer]):折叠成一个值
func 是二元函数(接收 累积值, 当前元素)
从左往右"累积",最终得到一个值
折叠过程(无初始值):
reduce(f, [a, b, c, d])
= f(f(f(a, b), c), d)
→ 第一步用前两个元素,之后每步把"累积结果"和下一个元素喂给 f
reduce(lambda acc, x: acc + x, [1,2,3,4])
step1: f(1, 2) = 3
step2: f(3, 3) = 6
step3: f(6, 4) = 10
→ 10
带初始值:
reduce(f, [a,b,c], init)
= f(f(f(init, a), b), c)
→ 从 init 开始折叠
reduce(lambda acc,x: acc+x, [1,2,3], 100) → 106
好处:① 空序列时返回 init(无初始值时空序列报错)
② 累积值和元素类型可不同(如累积到字典/集合)
在 functools 里(不是内置):
from functools import reduce
→ Python 3 把 reduce 从内置移到 functools
(Guido 认为可读性差,不该鼓励)
reduce 的问题:
① 可读性差——要在脑子里模拟折叠过程
② 大多有更好的替代(见后)
真正适合 reduce 的场景:
没有现成内置、自定义的折叠逻辑:
reduce(lambda acc, d: {**acc, **d}, list_of_dicts, {}) # 合并字典
reduce(operator.or_, list_of_sets) # 并集
所以 reduce(f,it,init)用二元函数从左折叠成一个值;在 functools 里;可读性差
reduce(func, iterable[, initializer]) 折叠成一个值——func 是二元函数(接收累积值、当前元素)、从左往右累积。折叠过程(无初始值):reduce(f, [a,b,c,d]) = f(f(f(a,b),c),d)(第一步用前两个、之后每步把累积结果和下一个喂给 f);reduce(lambda acc,x: acc+x, [1,2,3,4]):f(1,2)=3 → f(3,3)=6 → f(6,4)=10。带初始值:reduce(f, [a,b,c], init) = f(f(f(init,a),b),c)(从 init 开始,好处是空序列返回 init、累积值和元素类型可不同)。在 functools 里(不是内置):Python 3 把 reduce 移到 functools(Guido 认为可读性差)。reduce 的问题:可读性差(要脑内模拟折叠)、大多有更好替代。真正适合 reduce 的场景:没有现成内置的自定义折叠(合并字典 reduce(lambda acc,d: {**acc,**d}, ...)、并集 reduce(operator.or_, sets))。理解「reduce(f,it,init)二元函数从左折叠成一个值(f(f(a,b),c));带 init 从 init 开始(空序列返 init、类型可不同);在 functools 里;可读性差、有更好替代;适合自定义折叠(合并字典/并集)」,就掌握了 reduce。
四、和列表推导式的对比
理解为什么推导式更 Pythonic:
map/filter vs 列表推导式:
转换:
map(lambda x: x*2, it) → [x*2 for x in it]
map(str, it) → [str(x) for x in it] 或 map 略简洁
筛选:
filter(lambda x: x>0, it) → [x for x in it if x>0]
筛选+转换:
map(f, filter(g, it)) → [f(x) for x in it if g(x)]
→ 推导式一行搞定、可读性远好于嵌套 map/filter
为什么推导式更 Pythonic:
① 直观——读起来像"对每个 x,如果...,取..."
② 一体——筛选+转换在一个表达式里(map+filter 要嵌套)
③ 不用写 lambda(map/filter 常要 lambda)
④ 可生成 list/set/dict/生成器(推导式统一语法)
map/filter 仍有优势的场景:
① 现成函数(不用 lambda):map(str, nums)、map(int, strs)
② 惰性省内存:map/filter 是迭代器(推导式的 [] 是即时列表)
→ 大数据用 (x for x in it)(生成器表达式)也惰性
③ 函数式风格代码库
惰性对比:
map(f, it) # 惰性迭代器
[f(x) for x in it] # 即时列表(占内存)
(f(x) for x in it) # 生成器表达式(惰性,等价 map)
选择:
一般转换/筛选 → 列表推导式(或生成器表达式)
现成函数映射 → map(略简洁)
大数据惰性 → 生成器表达式 或 map/filter
所以推导式更 Pythonic(直观/一体/不写 lambda);map 现成函数时简洁、惰性省内存
map/filter vs 列表推导式:转换 map(lambda x: x*2, it) → [x*2 for x in it]、筛选 filter(lambda x: x>0, it) → [x for x in it if x>0]、筛选+转换 map(f, filter(g, it)) → [f(x) for x in it if g(x)](推导式一行、可读性远好于嵌套)。为什么推导式更 Pythonic:① 直观、② 一体(筛选+转换在一个表达式)、③ 不用写 lambda、④ 可生成 list/set/dict/生成器。map/filter 仍有优势的场景:① 现成函数(map(str, nums) 不用 lambda)、② 惰性省内存(map/filter 是迭代器、大数据用生成器表达式也惰性)、③ 函数式风格代码库。惰性对比:map(f, it) 惰性、[f(x) for x in it] 即时列表占内存、(f(x) for x in it) 生成器表达式惰性(等价 map)。理解「推导式更 Pythonic(直观/一体/不写 lambda/可生成各种);map 现成函数时简洁、惰性省内存;大数据用生成器表达式;一般转换筛选用推导式」,就掌握了这个对比。
五、reduce 的替代方案
理解用内置替代 reduce:
reduce 的更好替代(能用就别用 reduce):
求和:
reduce(lambda a,b: a+b, nums) → sum(nums)
连乘:
reduce(lambda a,b: a*b, nums) → math.prod(nums) # 3.8+
最大/最小:
reduce(max, nums) → max(nums)
reduce(min, nums) → min(nums)
字符串拼接:
reduce(lambda a,b: a+b, strs) → "".join(strs)
⚠️ reduce 拼字符串是 O(n²)(每次新建),join 是 O(n)
累积中间结果(要每一步的结果):
itertools.accumulate(nums) # 前缀和 [1,3,6,10]
itertools.accumulate(nums, func) # 自定义累积
→ reduce 只给最终值、accumulate 给每步
集合并/交:
reduce(operator.or_, sets) → set().union(*sets)
reduce(operator.and_, sets) → ...intersection
为什么优先内置:
① 有名字(sum/max/join 一看就懂)
② 更快(C 实现)
③ 更安全(join 避免 O(n²)、sum 有优化)
reduce 真正的用武之地:
自定义的、没有现成内置的折叠逻辑
reduce(merge_func, items, initial)
所以 reduce 用 sum/max/prod/join/accumulate 替代(有名字+更快+更安全)
reduce 的更好替代(能用就别用 reduce):求和 sum(nums)、连乘 math.prod(nums)(3.8+)、最大/最小 max/min(nums)、字符串拼接 "".join(strs)(reduce 拼字符串是 O(n²)、join 是 O(n))、累积中间结果 itertools.accumulate(nums)(reduce 只给最终值、accumulate 给每步)、集合并/交 set().union(*sets)。为什么优先内置:① 有名字(sum/max/join 一看就懂)、② 更快(C 实现)、③ 更安全(join 避免 O(n²))。reduce 真正的用武之地:自定义的、没有现成内置的折叠逻辑。理解「reduce 替代:求和 sum、连乘 prod、极值 max/min、拼接 join(避免 O(n²))、累积 accumulate(给每步)、集合 union;内置有名字+更快+更安全;reduce 只用于自定义折叠」,就掌握了 reduce 的替代。
六、总结与实践
总结三个函数:
三件套:
map(f, it) 映射:每个元素应用 f
filter(f, it) 过滤:保留 f 为真的
reduce(f, it) 归约:折叠成一个值(在 functools 里)
返回值:
map/filter → 惰性迭代器(省内存、只遍历一次)
reduce → 单个值
实践建议:
① 转换/筛选优先用列表推导式(或生成器表达式)
[f(x) for x in it if cond]
② map 仅在"现成函数"时用(map(str, nums))
③ reduce 尽量避免——用 sum/max/min/prod/join/accumulate
④ 大数据用生成器表达式/map/filter(惰性省内存)
惰性提醒:
map/filter 只能遍历一次(要复用先 list())
vs 推导式对照:
map(f, it) → [f(x) for x in it]
filter(f, it) → [x for x in it if f(x)]
map(f, filter(g,it)) → [f(x) for x in it if g(x)]
核心总结:
map 映射/filter 过滤/reduce 归约
map/filter 惰性迭代器、reduce 在 functools
多数场景用列表推导式更 Pythonic
reduce 能用 sum/max/join 就别用
所以 map/filter/reduce 三件套,推导式更 Pythonic,reduce 用 sum/join 替代
三件套:map(映射)、filter(过滤)、reduce(归约、在 functools 里)。返回值:map/filter 惰性迭代器、reduce 单个值。实践建议:① 转换/筛选优先列表推导式(或生成器表达式)、② map 仅在现成函数时用、③ reduce 尽量避免(用 sum/max/prod/join/accumulate)、④ 大数据用生成器表达式/map/filter 惰性省内存。惰性提醒:map/filter 只能遍历一次(复用先 list)。理解「map 映射/filter 过滤/reduce 归约;map/filter 惰性、reduce 在 functools;多数用列表推导式更 Pythonic;reduce 用 sum/max/join 替代;大数据用生成器表达式」,就掌握了总结与实践。
记忆钩子:「map/filter/reduce 是函数式三件套:①map(f,it)把 f 作用到每个元素(映射/转换),多个可迭代并行取(以最短为准)②filter(f,it)保留 f 返回真的元素(过滤),★filter(None,it)去除所有假值(0/”/None/[])③reduce(f,it,init)用二元函数从左往右折叠成一个值(reduce(lambda a,b:a+b,[1,2,3,4])=((1+2)+3)+4=10,带 init 从 init 开始、空序列返 init);★关键:map/filter 在 Python 3 返回『惰性迭代器』(不是列表、省内存、只能遍历一次,复用先 list()),reduce 在 functools 里(Python 3 从内置移走,Guido 嫌可读性差);★和列表推导式比:多数场景推导式更 Pythonic([f(x)for x in it]比 map(lambda)直观、[x for x in it if c]比 filter 清晰、筛选+转换一行搞定),但 map 用『现成函数』时更简洁(map(str,nums)不用写 lambda)、且惰性省内存;★reduce 尽量避免——能用 sum(求和)/max·min(极值)/math.prod(连乘 3.8+)/”.join(拼接,避免 reduce 的 O(n²))/itertools.accumulate(要每步中间结果)就别用,reduce 只留给自定义折叠逻辑」。
七、常见误区与追问
- 误区:map(f, it) 返回一个列表。 在 Python 3 返回的是惰性迭代器(map 对象),不是列表;
map(str, [1,2,3])打印是<map object>,要列表得list(map(str, [1,2,3]));这是 Python 2 到 3 的变化(Py2 的 map/filter 返回列表);惰性迭代器省内存(大数据友好)但只能遍历一次(遍历完就空)、也不能索引。 - 误区:reduce 是内置函数,直接就能用。 在 Python 3 里 reduce 被移到了
functools模块,要from functools import reduce才能用(Python 2 里它才是内置);Guido 当年主张移走它、认为它可读性差(要在脑子里模拟折叠过程),鼓励用有名字的替代(sum、max、join 等);所以看到直接用 reduce 而没 import 的代码,多半是 Python 2 遗留或会报 NameError。 - 误区:用 reduce 拼接字符串和用 join 一样。 性能差很多——
reduce(lambda a,b: a+b, strs)每次a+b都创建一个新字符串(字符串不可变),总复杂度 O(n²);"".join(strs)先算总长度、一次性分配、O(n);所以拼接字符串永远用 join,不要用 reduce 或循环+=(同样 O(n²) 风险);这也是「能用专门的内置就别用 reduce」的典型例子。 - 误区:map 和 filter 只能配合 lambda 使用。 不是——map/filter 接受任何可调用对象,用「现成的函数/内置函数」时反而是它们最简洁的场景:
map(str, nums)、map(int, str_list)、map(len, lists)、filter(str.isdigit, chars)、filter(None, it)(去假值)都不用写 lambda;反过来,如果需要写 lambda(map(lambda x: x*2, nums)),那还不如用列表推导式[x*2 for x in nums]直观。 - 追问:map/filter/reduce 和列表推导式相比,什么时候该用哪个? 一般的转换和筛选优先用列表推导式(或生成器表达式)——
[f(x) for x in it]、[x for x in it if cond]、[f(x) for x in it if g(x)],因为它更直观、能把筛选和转换写在一个表达式里、不用写 lambda、还能统一生成 list/set/dict/生成器;map 只在「映射函数是现成函数/内置」时更简洁(map(str, nums));filter 基本都能被推导式替代(filter(None, it)用[x for x in it if x]);reduce 尽量避免、用 sum/max/min/prod/join/accumulate 这些有名字的内置;需要惰性省内存时用生成器表达式(f(x) for x in it)或 map/filter(它们本身惰性)。 - 追问:reduce 的折叠过程是怎样的?带初始值有什么用? 不带初始值时,
reduce(f, [a,b,c,d])=f(f(f(a,b),c),d)——先用前两个元素调 f 得到累积值,然后每一步把「当前累积值」和「下一个元素」喂给 f,从左往右直到结束,返回最终累积值;比如reduce(lambda acc,x: acc+x, [1,2,3,4])依次算 1+2=3、3+3=6、6+4=10。带初始值reduce(f, it, init)=f(f(f(init,a),b),c),从 init 开始折叠,有两个好处:① 空序列时返回 init(不带初始值时对空序列会抛 TypeError);② 累积值可以和元素是不同类型(比如把一列字典折叠合并成一个字典reduce(lambda acc,d: {**acc,**d}, dicts, {}),初始值{}指定了累积容器的类型);所以处理可能为空的序列、或累积到不同类型的容器时要给初始值。 - 追问:想要累积过程中「每一步」的结果(不只是最终值)该用什么? 用
itertools.accumulate而不是 reduce:reduce只返回最终的折叠值,而accumulate(iterable[, func])返回一个迭代器、产出「每一步的累积结果」——list(itertools.accumulate([1,2,3,4]))得到[1,3,6,10](前缀和),accumulate([1,2,3,4], lambda a,b: a*b)得到[1,2,6,24](前缀积),还能指定 initial 参数(3.8+);典型用途是前缀和/前缀积、运行最大值(accumulate(nums, max))、累计余额等需要「中间过程」的场景;简单说 reduce 给终点、accumulate 给沿途每个点。
八、加强记忆
map、filter、reduce 是函数式三件套:① map(f, it) 把 f 作用到每个元素(映射/转换,多个可迭代并行取、以最短为准);② filter(f, it) 保留 f 返回真的元素(过滤,filter(None, it) 去除所有假值 0/""/None/[]);③ reduce(f, it, init) 用二元函数从左往右折叠成一个值(reduce(lambda a,b: a+b, [1,2,3,4]) = ((1+2)+3)+4 = 10,带 init 从 init 开始、空序列返 init)。关键:map/filter 在 Python 3 返回「惰性迭代器」(不是列表、省内存、只能遍历一次、复用先 list()),reduce 在 functools 里(Python 3 从内置移走,Guido 嫌可读性差)。和列表推导式比:多数场景推导式更 Pythonic([f(x) for x in it] 比 map(lambda) 直观、筛选+转换一行搞定),但 map 用「现成函数」时更简洁(map(str, nums) 不用写 lambda)、且惰性省内存。reduce 尽量避免——能用 sum(求和)/max·min(极值)/math.prod(连乘)/"".join(拼接,避免 reduce 的 O(n²))/itertools.accumulate(要每步中间结果)就别用,reduce 只留给自定义折叠。一句话「map 映射/filter 过滤(filter(None)去假值)/reduce 归约折叠;map/filter 返回惰性迭代器只遍历一次、reduce 在 functools;多数用列表推导式更 Pythonic、map 现成函数时简洁;reduce 用 sum/max/join/accumulate 替代」。