资讯详情

Python的化简函数reduce()详解

📅 2026/10/10 20:07:35 | 华诺云谱 👁 阅读
Python的化简函数reduce()详解
前言先纠正题面里一个已经不成立的前提reduce()在 Python 3 里不是内置函数。Python 3.0 的变更说明里写得很直接——「Removedreduce(). Usefunctools.reduce()if you really need it; however, 99 percent of the time an explicitforloop is more readable.」也就是说reduce只在 Python 2 里是内置的Python 2.7 已于 2020 年 1 月 1 日停止维护今天写reduce(...)而不导入会直接得到NameError: name reduce is not defined。正确写法是from functools import reduce。另一个误解是「reduce是 Python 里做聚合的正统方式」。官方文档自己都在劝退对求和、求最大最小、求积这类需求sum、max、math.prod更清楚也更快reduce该留给真正需要自定义「从左到右累积」逻辑的场合。本文讲清四件事reduce的确切签名和语义、三个边界行为的处理、什么时候该用别的函数替代以及几个确实值得用reduce的例子。示例以 Python 3 为基准initial作为关键字参数需要 Python 3.14在此之前只能按位置传。一、签名与语义官方签名是functools.reduce(function, iterable, /[, initial])文档给出的近似实现是这样下面这段是官方文档中的等价代码可以直接跑# 摘自官方文档适用于 Python 3.0initial_missing object()def reduce(function, iterable, /, initialinitial_missing):it iter(iterable)if initial is initial_missing:value next(it)else:value initialfor element in it:value function(value, element)return value语义可以概括为一句话把iterable的第一个元素当作初始累积值若提供了initial则用它然后从左到右依次执行value function(value, element)最终返回value。# 适用于 Python 3.0from functools import reduceprint(reduce(lambda x, y: x y, [1, 2, 3, 4, 5]))# 15 —— 计算过程是 ((((12)3)4)5)function的两个参数有明确分工左边是累积值右边是来自可迭代对象的新元素。这一点在非交换运算减法、除法、字符串拼接、集合运算里非常关键。# 适用于 Python 3.0from functools import reduceprint(reduce(lambda x, y: x - y, [10, 1, 2])) # 7 —— ((10-1)-2)print(reduce(lambda x, y: y - x, [10, 1, 2])) # 11 —— ((1-10) 之后再来一次)二、三个必须记住的边界行为情况行为说明iterable为空未提供initial抛TypeError没有初始值可用iterable为空提供了initial返回initialfunction一次都不调用iterable只有一个元素未提供initial直接返回该元素function一次都不调用iterable只有一个元素提供了initial返回function(initial, 元素)会调用一次长可迭代对象全程惰性迭代不复制整个序列可接受生成器# 适用于 Python 3.0from functools import reducedef add(x, y):print(f 调用 add({x}, {y}))return x yprint(reduce(add, [7])) # 没有任何输出直接返回 7print(reduce(add, [], 0)) # 没有任何输出直接返回 0reduce的第一个参数是二元函数——只接受两个参数。写成三参数会报TypeError提示这个函数需要 3 个参数但只收到了 2 个。三、用reduce之前先看这张表reduce能做的事里绝大多数有更专用的替代品需求推荐写法备注求和sum(iterable, start0)start可位置可关键字求积math.prod(iterable, *, start1)需要 Python 3.8求最大 / 最小max(iterable, *, key..., default...)空序列用default逻辑与 / 或all(iterable)/any(iterable)短路求值遇到结果就停字符串拼接.join(parts)比反复x y高效得多保留每步中间结果itertools.accumulate(iterable, func)返回迭代器字典合并{**a, **b}3.9 也可用字典合并运算符展平一层嵌套列表推导式如[x for sub in data for x in sub]自定义累积逻辑functools.reduce真正该用它的时候一句话原则能用sum/max/any/join表达的就不要用reduce。官方文档那句「99% 的情况显式for循环更可读」说的就是这个意思。实战一需要自定义累积时reduce真正合适的地方是聚合规则本身没有一个内置函数能表达。# 适用于 Python 3.0from functools import reduceimport math# 1) 求一组数的最大公约数print(reduce(math.gcd, [12, 18, 24])) # 6# 2) 求一组区间的交集ranges [{1, 2, 3, 4}, {2, 3, 4, 5}, {3, 4, 5, 6}]print(reduce(lambda a, b: a b, ranges)) # {3, 4}# 3) 把多个字典合并后者覆盖前者dicts [{a: 1}, {b: 2}, {a: 9}]print(reduce(lambda a, b: {**a, **b}, dicts)) # {a: 9, b: 2}# 4) 按顺序依次应用一批函数funcs [lambda s: s.strip(), lambda s: s.upper(), lambda s: s.replace( , _)]print(reduce(lambda acc, f: f(acc), funcs, hello world )) # HELLO_WORLD# 5) 求「笛卡尔积式」的连乘print(reduce(lambda a, b: a * b, [1, 2, 3, 4], 1)) # 24第 3 个例子要留意{**a, **b}每次都会新建字典元素多时开销明显直接写for循环用一个字典原地update会更好。第 4 个例子是reduce在函数式编程里的经典用法——把一串变换函数折叠成一次调用。它比嵌套调用f3(f2(f1(x)))更适合函数列表长度不固定的时候。实战二reduce与accumulate的区别一个常见的混淆想要「每一步的中间结果」应该用itertools.accumulate而不是在reduce里手动收集。# 适用于 Python 3.0from functools import reduceimport itertoolsnums [1, 2, 3, 4, 5]# reduce 只给最终值print(reduce(lambda a, b: a b, nums)) # 15# accumulate 给每一步默认 func 就是 operator.addprint(list(itertools.accumulate(nums))) # [1, 3, 6, 10, 15]# 也可以自己指定 func位置参数import operatorprint(list(itertools.accumulate(nums, operator.mul))) # [1, 2, 6, 24, 120]# initial 是仅关键字参数需要 Python 3.8print(list(itertools.accumulate(nums, initial0))) # [0, 1, 3, 6, 10, 15]accumulate返回的是迭代器initial是仅关键字参数func是位置或关键字参数——这几点和reduce的initial处理方式不同别记混。常见坑点1. 直接调用reduce而不导入❌ 错误写法print(reduce(lambda x, y: x y, [1, 2, 3]))# NameError: name reduce is not defined✅ 正确写法from functools import reduceprint(reduce(lambda x, y: x y, [1, 2, 3])) # 6reduce在 Python 2 里是内置函数Python 3.0 已把它移出内置命名空间。2. 空序列没给initial❌ 错误写法from functools import reducereduce(lambda x, y: x y, []) # TypeError✅ 正确写法reduce(lambda x, y: x y, [], 0) # 03. 把initial当成只能位置传的参数写反顺序❌ 错误写法reduce(lambda x, y: x y, 0, [1, 2]) # 参数顺序错了✅ 正确写法顺序固定是「函数、可迭代对象、可选的 initial」reduce(lambda x, y: x y, [1, 2], 0) # 3需要提醒的是initial只有在 Python 3.14 起才能写成initial0这种关键字形式3.13 及更早的版本里它只能按位置传。4. 用reduce做字符串拼接❌ 错误写法from functools import reduceparts [a, b, c] * 10000s reduce(lambda x, y: x y, parts) # 反复创建中间字符串✅ 正确写法s .join(parts)join只分配一次结果reduce的x y会生成大量用后即弃的中间字符串。5. 误以为reduce会复制序列❌ 错误想法以为reduce是「先复制整个列表再操作」。✅ 正确认识reduce用迭代器逐个取元素不复制序列因此可以直接接收生成器from functools import reduceprint(reduce(lambda x, y: x y, (i for i in range(5)), 0)) # 106. 交换了累积值与新元素的位置❌ 错误写法减法、除法、字符串拼接等非交换运算会算错from functools import reduceprint(reduce(lambda x, y: y - x, [10, 1, 2])) # 11和直觉里的减法结果完全不同✅ 正确写法左边是累积值右边是新元素print(reduce(lambda x, y: x - y, [10, 1, 2])) # 77. 用可变对象做累积器导致原地副作用❌ 错误写法from functools import reducedef collect(acc, x):acc.append(x) # 原地修改且返回值与 acc 是同一对象return accprint(reduce(collect, [1, 2, 3], [])) # [1, 2, 3]reduce里做原地修改本身能跑但会掩盖「累积器被复用」的事实一旦把初始值换成外部传进来的列表就会污染调用方。这种情况用显式for循环表达得更清楚。✅ 正确写法要么纯函数式地返回新对象要么直接用forresult []for x in [1, 2, 3]:result.append(x)8. 在reduce里嵌套lambda做函数复合可读性崩掉❌ 错误写法from functools import reducecompose reduce(lambda f, g: lambda x: f(g(x)), [f1, f2, f3])嵌套三层lambda之后堆栈里全是lambda出错时完全看不出是哪一步。✅ 正确写法给内层复合函数起个名字from functools import reducedef compose_two(f, g):def composed(x):return f(g(x))return composedcompose reduce(compose_two, [f1, f2, f3])总结项目结论模块functools.reducePython 3 里不是内置函数签名functools.reduce(function, iterable, /[, initial])参数顺序左边累积值、右边新元素空序列无initial抛TypeError有initial返回它单元素无initial时直接返回该元素function不调用initial关键字需要 Python 3.14之前只能按位置传首选替代sum/max/any/all/.join/math.prod要中间结果用itertools.accumulate不是reducereduce的定位很明确当聚合规则没有一个内置函数能表达、且从左到右折叠的语义正好合适时才用它。记住三条硬事实——它住在functools里、function必须收两个参数、空序列必须先给initial再用「求和求最值优先用内置函数」这条原则过滤一遍绝大多数误用都能避免。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑