在 Python 的函数式工具中,map 负责逐元素映射,filter 负责筛选,而 functools.reduce 负责把可迭代对象折叠成一个结果。它的执行方式是把序列元素两两聚合:先算前两个,再把结果与下一个继续算,直到只剩下一个累积值。这个思路适合累加、累乘、求最值、配置合并和流水线处理。
一、语法和工作原理
- from functools import reduce
- # reduce(function, iterable[, initializer])
- # function: 二元函数,参数为(累积值, 下一个元素)
- # iterable: 可迭代对象
- # initializer: 可选初始累积值
复制代码
没有 initializer 时,累积值先取第一个元素,再遍历剩余元素;有 initializer 时,累积值从 initializer 开始,再遍历所有元素。空序列且没有 initializer 会抛 TypeError:reduce() of empty iterable with no initial value。所以实际项目里建议提供 initializer,让空数据也有确定结果。
- from functools import reduce
- numbers = [1, 2, 3, 4, 5]
- def add_and_show(a, b):
- result = a + b
- print(f'{a} + {b} = {result}')
- return result
- total = reduce(add_and_show, numbers)
- print(f'最终结果: {total}')
- # 1 + 2 = 3
- # 3 + 3 = 6
- # 6 + 4 = 10
- # 10 + 5 = 15
- # 最终结果: 15
复制代码
initializer 会改变起始值,但不改变计算规则。例如初始值为 0 时结果可能相同,初始值为 10 时会在原结果上多加 10;空列表配合初始值 0 可安全返回 0。
- from functools import reduce
- numbers = [1, 2, 3, 4, 5]
- print(reduce(lambda a, b: a + b, numbers)) # 15
- print(reduce(lambda a, b: a + b, numbers, 0)) # 15
- print(reduce(lambda a, b: a + b, numbers, 10)) # 25
- empty_list = []
- # reduce(lambda a, b: a + b, empty_list) # TypeError
- print(reduce(lambda a, b: a + b, empty_list, 0)) # 0
复制代码
二、聚合计算与数学运算
用 operator 模块里的 add、mul,可以让聚合代码更直观:累加、累乘、找最大最小值、拼接字符串都可由 reduce 完成。字符串拼接时,lambda a, b: a + b 得到无分隔结果,lambda a, b: f'{a} {b}' 得到空格分隔结果。
- from functools import reduce
- import operator
- import math
- numbers = [1, 2, 3, 4, 5]
- print(reduce(operator.add, numbers)) # 15
- print(reduce(operator.mul, numbers)) # 120
- print(reduce(lambda a, b: a if a > b else b, numbers)) # 5
- print(reduce(lambda a, b: a if a < b else b, numbers)) # 1
- words = ['Python', '是', '一门', '优雅的', '语言']
- print(reduce(lambda a, b: a + b, words)) # Python是一门优雅的语言
- print(reduce(lambda a, b: f'{a} {b}', words)) # Python 是 一门 优雅的 语言
复制代码
阶乘、最大公约数、最小公倍数也有对应实现。阶乘用 reduce(operator.mul, range(1, n + 1));最大公约数用 reduce(math.gcd, numbers);最小公倍数用 reduce(math.lcm, numbers),其中 math.lcm 需要 Python 3.9+。注意空列表要提前校验,否则 reduce 会缺少初始值。
- from functools import reduce
- import operator
- import math
- def factorial(n):
- if n < 0:
- raise ValueError('阶乘只对非负整数定义')
- if n == 0:
- return 1
- return reduce(operator.mul, range(1, n + 1))
- print(factorial(5)) # 120
- print(factorial(10)) # 3628800
- def gcd_of_list(numbers):
- if not numbers:
- raise ValueError('列表不能为空')
- return reduce(math.gcd, numbers)
- print(gcd_of_list([48, 64, 96])) # 16
- print(gcd_of_list([100, 75, 25])) # 25
- print(gcd_of_list([17, 31])) # 1
- def lcm_of_list(numbers):
- if not numbers:
- raise ValueError('列表不能为空')
- return reduce(math.lcm, numbers) # Python 3.9+
- print(lcm_of_list([4, 6, 8])) # 24
复制代码
三、模拟 map 和 filter 只适合理解原理
reduce 可以通过累积列表模拟 map 和 filter,但实际开发中不要这样写,因为 map、filter、列表推导式更直接。
- from functools import reduce
- def my_map(func, iterable):
- return reduce(lambda acc, item: acc + [func(item)], iterable, [])
- print(my_map(lambda x: x ** 2, [1, 2, 3, 4, 5])) # [1, 4, 9, 16, 25]
- def my_filter(func, iterable):
- return reduce(lambda acc, item: acc + [item] if func(item) else acc, iterable, [])
- print(my_filter(lambda x: x % 2 == 0, range(1, 11))) # [2, 4, 6, 8, 10]
复制代码
四、高级场景:流水线和字典合并
管道处理是 reduce 比较有价值的地方:把多个数据处理函数放进列表,再用 reduce(lambda data, step: step(data), pipeline, raw_data) 依次调用。原文示例先去掉 None,再转整数,再过滤正数,最后乘以 2,得到 [2, 6, 10, 16]。
- from functools import reduce
- def remove_none(data):
- return [x for x in data if x is not None]
- def convert_to_int(data):
- return [int(x) for x in data]
- def filter_positive(data):
- return [x for x in data if x > 0]
- def multiply_by(data, factor):
- return [x * factor for x in data]
- pipeline = [
- remove_none,
- convert_to_int,
- filter_positive,
- lambda data: multiply_by(data, 2),
- ]
- raw_data = ['1', None, '3', '-2', '5', None, '0', '8']
- result = reduce(lambda data, step: step(data), pipeline, raw_data)
- print(result) # [2, 6, 10, 16]
复制代码
字典合并也可以使用 reduce。浅合并用 lambda a, b: {**a, **b},后面的键覆盖前面的键。嵌套字典需要自己实现 deep_merge:遇到两边都是 dict 的键时递归合并,否则直接覆盖。
- from functools import reduce
- dicts = [
- {'host': 'localhost', 'port': 8080},
- {'port': 9090, 'debug': True},
- {'timeout': 30, 'retries': 3},
- ]
- merged = reduce(lambda a, b: {**a, **b}, dicts)
- print(merged)
- # {'host': 'localhost', 'port': 9090, 'debug': True, 'timeout': 30, 'retries': 3}
- configs = [
- {'database': {'host': 'localhost', 'port': 5432}, 'debug': False},
- {'database': {'port': 5433, 'name': 'mydb'}, 'cache': True},
- {'debug': True, 'timeout': 30},
- ]
- def deep_merge(a, b):
- result = dict(a)
- for key, value in b.items():
- if key in result and isinstance(result[key], dict) and isinstance(value, dict):
- result[key] = deep_merge(result[key], value)
- else:
- result[key] = value
- return result
- final_config = reduce(deep_merge, configs, {})
- print(final_config)
- # {'database': {'host': 'localhost', 'port': 5433, 'name': 'mydb'}, 'debug': True, 'cache': True, 'timeout': 30}
复制代码
五、reduce 与内置函数的取舍
能用内置函数时优先用内置函数。sum、math.prod、max、''.join 通常比 reduce 更清晰,也更容易被其他开发者理解。只有复杂聚合逻辑或流水线场景,reduce 才更自然。若 reduce 让代码难读,应改回 for 循环。
- from functools import reduce
- import operator
- import math
- numbers = [1, 2, 3, 4, 5]
- total1 = reduce(operator.add, numbers) # 15
- total2 = sum(numbers) # 15,更清晰
- product1 = reduce(operator.mul, numbers) # 120
- product2 = math.prod(numbers) # 120,Python 3.8+
- max1 = reduce(lambda a, b: a if a > b else b, numbers) # 5
- max2 = max(numbers) # 5,更好
- words = ['a', 'b', 'c']
- concat1 = reduce(lambda a, b: a + b, words) # 'abc'
- concat2 = ''.join(words) # 'abc',更好
复制代码
六、总结
reduce(func, iterable[, initializer]) 的核心是把序列累积为单个值。建议始终提供 initializer,避免空序列报错;简单聚合优先用 sum、max、math.prod 等内置函数;reduce 模拟 map/filter 仅作学习;管道处理和深度合并字典是较实用的高级场景。当需要把一组数据折叠成一个结果时,再考虑 reduce。 |