☰
Python的化简函数reduce()详解
2026/10/10 20:07:11 网站建设 项目流程

前言


先纠正题面里一个已经不成立的前提: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.0+)
initial_missing = object()

def reduce(function, iterable, /, initial=initial_missing):
it = iter(iterable)
if initial is initial_missing:
value = next(it)
else:
value = initial
for element in it:
value = function(value, element)
return value

语义可以概括为一句话:把iterable的第一个元素当作初始累积值(若提供了initial则用它),然后从左到右依次执行value = function(value, element),最终返回value。


# 适用于 Python 3.0+
from functools import reduce

print(reduce(lambda x, y: x + y, [1, 2, 3, 4, 5]))
# 15 —— 计算过程是 ((((1+2)+3)+4)+5)

function的两个参数有明确分工:左边是累积值,右边是来自可迭代对象的新元素。这一点在非交换运算(减法、除法、字符串拼接、集合运算)里非常关键。


# 适用于 Python 3.0+
from functools import reduce

print(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.0+
from functools import reduce

def add(x, y):
print(f" 调用 add({x}, {y})")
return x + y

print(reduce(add, [7])) # 没有任何输出,直接返回 7
print(reduce(add, [], 0)) # 没有任何输出,直接返回 0

reduce的第一个参数是二元函数——只接受两个参数。写成三参数会报TypeError,提示这个函数需要 3 个参数但只收到了 2 个。


三、用reduce之前先看这张表


reduce能做的事里,绝大多数有更专用的替代品:




需求推荐写法备注



求和sum(iterable, start=0)start可位置可关键字

求积math.prod(iterable, *, start=1)需要 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.0+
from functools import reduce
import 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.0+
from functools import reduce
import itertools

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

# reduce 只给最终值
print(reduce(lambda a, b: a + b, nums)) # 15

# accumulate 给每一步(默认 func 就是 operator.add)
print(list(itertools.accumulate(nums))) # [1, 3, 6, 10, 15]

# 也可以自己指定 func(位置参数)
import operator
print(list(itertools.accumulate(nums, operator.mul))) # [1, 2, 6, 24, 120]

# initial 是仅关键字参数,需要 Python 3.8+
print(list(itertools.accumulate(nums, initial=0))) # [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 reduce

print(reduce(lambda x, y: x + y, [1, 2, 3])) # 6

reduce在 Python 2 里是内置函数,Python 3.0 已把它移出内置命名空间。


2. 空序列没给initial


❌ 错误写法:


from functools import reduce

reduce(lambda x, y: x + y, []) # TypeError

✅ 正确写法:


reduce(lambda x, y: x + y, [], 0) # 0

3. 把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 起才能写成initial=0这种关键字形式;3.13 及更早的版本里它只能按位置传。


4. 用reduce做字符串拼接


❌ 错误写法:


from functools import reduce

parts = ["a", "b", "c"] * 10000
s = reduce(lambda x, y: x + y, parts) # 反复创建中间字符串

✅ 正确写法:


s = "".join(parts)

join只分配一次结果,reduce的x + y会生成大量用后即弃的中间字符串。


5. 误以为reduce会复制序列


❌ 错误想法:以为reduce是「先复制整个列表再操作」。


✅ 正确认识:reduce用迭代器逐个取元素,不复制序列,因此可以直接接收生成器:


from functools import reduce

print(reduce(lambda x, y: x + y, (i for i in range(5)), 0)) # 10

6. 交换了累积值与新元素的位置


❌ 错误写法(减法、除法、字符串拼接等非交换运算会算错):


from functools import reduce

print(reduce(lambda x, y: y - x, [10, 1, 2])) # 11,和直觉里的减法结果完全不同

✅ 正确写法:左边是累积值,右边是新元素:


print(reduce(lambda x, y: x - y, [10, 1, 2])) # 7

7. 用可变对象做累积器,导致原地副作用


❌ 错误写法:


from functools import reduce

def collect(acc, x):
acc.append(x) # 原地修改,且返回值与 acc 是同一对象
return acc

print(reduce(collect, [1, 2, 3], [])) # [1, 2, 3]

reduce里做原地修改本身能跑,但会掩盖「累积器被复用」的事实,一旦把初始值换成外部传进来的列表,就会污染调用方。这种情况用显式for循环表达得更清楚。


✅ 正确写法:要么纯函数式地返回新对象,要么直接用for:


result = []
for x in [1, 2, 3]:
result.append(x)

8. 在reduce里嵌套lambda做函数复合,可读性崩掉


❌ 错误写法:


from functools import reduce

compose = reduce(lambda f, g: lambda x: f(g(x)), [f1, f2, f3])

嵌套三层lambda之后,堆栈里全是<lambda>,出错时完全看不出是哪一步。


✅ 正确写法:给内层复合函数起个名字:


from functools import reduce

def compose_two(f, g):
def composed(x):
return f(g(x))
return composed

compose = reduce(compose_two, [f1, f2, f3])

总结




项目结论



模块functools.reduce,Python 3 里不是内置函数

签名functools.reduce(function, iterable, /[, initial])

参数顺序左边累积值、右边新元素

空序列无initial抛TypeError;有initial返回它

单元素无initial时直接返回该元素,function不调用

initial关键字需要 Python 3.14+,之前只能按位置传

首选替代sum/max/any/all/"".join/math.prod

要中间结果用itertools.accumulate,不是reduce



reduce的定位很明确:当聚合规则没有一个内置函数能表达、且从左到右折叠的语义正好合适时才用它。记住三条硬事实——它住在functools里、function必须收两个参数、空序列必须先给initial;再用「求和求最值优先用内置函数」这条原则过滤一遍,绝大多数误用都能避免。




需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询