from functools importreduce
numbers =[2,4,6]
product =reduce(lambda x, y: x * y, numbers)# ((2 x 4) x 6) = 8 x 6 = 48
min_value =reduce(lambda x, y: x if x < y else y, numbers)# 2
words =['dog','cat','tree','pony']
str_concat =reduce(lambda x, y: x + y, words)# "dogcattreepony"技术支持
from functools importreduce
words =[]# Error: reduce() of empty iterable with no initial value
str_concat =reduce(lambda x, y: x + y, words)# Correct: use empty string initializer
str_concat =reduce(lambda x, y: x + y, words,"")技术支持
from functools importreduceimport re
# Define preprocessing stepsdefstrip_punctuation(s):return re.sub(r"[^\w\s]","", s)defto_lower(s):return s.lower()defremove_stopwords(s):
stops ={"the","is","a","of"}return" ".join(word for word in s.split()if word notin stops)defstem_words(s):# trivial "stemmer": cut off 'ing'return" ".join(word[:-3]if word.endswith("ing")else word for word in s.split())
pipeline =[
strip_punctuation,
to_lower,
remove_stopwords,
stem_words,]# Input data
text ="The quick brown fox is Jumping over a log."# Apply pipeline with reduce
processed =reduce(lambda acc, f: f(acc), pipeline, text)print(processed)# quick brown fox jump over log技术支持
from functools importreduceimport re
# Define preprocessing stepsdefstrip_punctuation(s):return re.sub(r"[^\w\s]","", s)defto_lower(s):return" ".join(map(str.lower, s.split()))defremove_stopwords(s):
stops ={"the","is","a","of"}return" ".join(filter(lambda w: w notin stops, s.split()))defstem_words(s):# trivial "stemmer": cut off 'ing'return" ".join(map(lambda w: w[:-3]if w.endswith("ing")else w, s.split()))# Pipeline of transformations
pipeline =[
strip_punctuation,
to_lower,
remove_stopwords,
stem_words,]# Input data
text ="The quick brown fox is Jumping over a log."# Apply pipeline with reduce
processed =reduce(lambda acc, f: f(acc), pipeline, text)print(processed)# quick brown fox jump over log技术支持
转换步骤包括:
去除标点符号。“The quick brown fox is Jumping over a log.” → “The quick brown fox is Jumping over a log”
转为小写
“The quick brown fox is Jumping over a log” → “the quick brown fox is jumping over a log”
移除停用词。“the quick brown fox is jumping over a log” → “quick brown fox jumping over log”
词干提取
“quick brown fox jumping log” → “quick brown fox jump log”
from functools importreduceimport numpy as np
defstandardize(x):return(x - x.mean())/(x.std()+1e-9)defclip(x):return np.clip(x,0,1)deflog(x):return np.log1p(x)
x = np.array([1,500,40.5,100,250.45])
funcs =[standardize, clip, log]
y =reduce(lambda a, f: f(a), funcs, x)# x is ndarray技术支持
from functools importreducedefadd_chars(acc, word):
acc.extend(word)# extend adds each character of the stringreturn acc
chars =reduce(add_chars,["hi","ok"],[])print(chars)# ['h', 'i', 'o', 'k']技术支持
尽管这段代码运行良好,静态分析器仍可能提出以下疑问。
初始化器 [] 的元素类型不明确。
分析器可能假设累加器与元素类型相同。
为让人和分析器更易理解,请添加类型注解。
from functools importreducefrom typing import List
defadd_chars(acc: List[str], word:str)-> List[str]:
acc.extend(word)# extend adds each character of the stringreturn acc
chars: List[str]=reduce(add_chars,["hi","ok"],[])print(chars)# ['h', 'i', 'o', 'k']技术支持
Python 的
reduce()函数源自函数式编程世界。 函数式编程(FP)是一种编程范式,其中程序通过对不可变数据应用函数来构建结果。这种风格中的一个常见模式是“折叠(fold)”,即将序列归约为单一结果。例如,将数字列表 [2, 4, 5, 3] 在加法下逐步折叠为 14: [2, 4, 5, 3] → [6, 5, 3] → [11, 3] → 14。
reduce()将这一思想进行泛化。它在可迭代对象上应用一个二元操作,直到只剩下结果。在本文中,我将探讨 Python
reduce()的关键要素,并给出一些实用示例。若您需要回顾 Python 基础,建议查看以下资源:历史
Python 中还包含其他函数式编程函数,例如
map()和filter()。诸如
map()和filter()等函数式构造自 Python 1.0 起便已存在。Guido van Rossum 并不喜欢它们,他指出reduce()难以理解,而for循环几乎总是更易读。按照 PEP 3100,在 Python 3.0 中,开发者将reduce()从内置函数中移除,并移至functools模块。这一调整实质上将它降级为小众工具。为何使用 reduce()?
大多数情况下,我认为使用内置函数或循环通常是更好的选择。不过,在某些用例中,
reduce()仍然是一个不错的选择。Python reduce() 的工作原理
让我们来看看
reduce()的机制。reduce()的基本函数签名为reduce()接受两个必需参数和一个可选的第三个参数。function:一个二元函数,指定如何组合两个元素,iterable:要归约的序列或可迭代对象,如列表或元组,initializer(可选):用于为函数提供种子的初始值。逐步示例:
最终结果是 13。
简单的 reduce() 示例
以下玩具示例演示了如何使用
reduce()的机制。初始化器(Initializer)
如果不提供初始化器,
reduce()会将可迭代对象的第一个元素作为起始值。若可迭代对象为空,reduce()会抛出TypeError。为使代码健壮,请提供初始化器以定义空输入时的行为。初始化器还可用于以空容器为结果“播种”。例如,您可以将单词连接为一个扁平的字符列表。
若要进一步探索 Python 与数据处理,以下是我推荐的一些选项。
定义归约器(reducer)
到目前为止,我们使用的是 lambda 函数来定义二元运算符。
您也可以使用
operator模块中的运算符。operator模块 提供常见运算符和方法调用的函数版本。例如,可以用operator.add(x, y)代替 x + y。这样您就可以将预定义(且高效)的运算符传入reduce(),无需编写 lambda。第三种选择是编写自定义函数。当没有预定义运算符,或函数复杂到不适合用 lambda 时,这是一个好办法。
例如,假设您想从列表中移除重复项,但保留首次出现的顺序。您可以定义一个归约器,仅在某项此前未出现过时才将其追加到列表中。
如需更多去重思路,请参考本教程。
Python reduce() 的性能
reduce()涉及性能方面的问题。函数调用开销
Python 的
reduce()会为每个元素调用一次我们的函数。对于拥有一百万项的列表,这意味着一百万次函数调用,每次都要创建栈帧、处理参数、更新引用计数。这会带来显著的开销。相比之下,像
sum这样的内置函数只需调用一次 C 函数,并在 C 循环内完成这一百万次加法。这种差异会使内置函数的速度快出几个数量级。缓存局部性与 CPU 效率问题
Reduce 还会在缓存局部性和 CPU 效率方面受限。
现代 CPU 每秒可执行数十亿条指令,但访问内存(RAM)的速度要慢几个数量级。为弥补这一差距,现代 CPU 具有缓存(L1、L2、L3),用来快速存储和访问数据。
这些缓存利用两种模式。
相比之下,
reduce()的每一步都需要通过指针遍历来寻找下一个元素,并进行 Python 函数调用,这会破坏局部性并让 CPU 停顿。内置函数与向量化函数通过在紧凑的 C 循环中运行来避免这个问题。若需回顾如何编写地道且高效的 Python 代码,请查看:
Python reduce() 的替代方案
内置函数:
reduce()带来的开销。随着输入规模增大,这种速度优势会进一步放大。sum、min),意图显而易见。调用reduce()则需要您解析被折叠的函数。循环:
reduce()更快。reduce()更易读。调用reduce()会迫使您解析一个函数式表达式,而循环更具 Python 风格。itertools.accumulate()Python 的
itertools库提供了一组高性能迭代器,例如count()、product()和combinations()。其中一个有用的itertools函数是itertools.accumulate()。与reduce()类似,它将函数折叠到可迭代对象上。然而,accumulate() 会存储计算的中间值,而不仅仅是最终结果。例如,
当您需要运行中的总和或最小值/最大值时,accumulate 非常有用。例如,您可能想知道各月的最高气温。
使用 reduce() 的常见陷阱
当您使用
reduce()时,请牢记以下陷阱。reduce()。reduce()生搬硬套到应当使用生成器或流式处理更高效的场景中。operator模块中的函数。Lambda 尤其在非结合性操作中会损害清晰度。Python reduce() 的最佳实践与指南
与任何工具一样,使用
reduce()也有最佳实践。如果您决定reduce()是合适的工具,请遵循以下指南。先设计归约器
min,是math.inf;对于集合并集,是set()。这样可以保持代码健壮,避免TypeErrors。保持简洁与可读
文档化
测试
监控性能
Python reduce() 的高级应用与真实用例
鉴于其劣势,
reduce()似乎毫无价值。事实并非如此,reduce()仍有许多有用的用例。处理嵌套结构
Reduce 提供了一种简洁的方法来遍历嵌套数据结构(如 JSON 对象),通过将一系列键折叠为连续查找。
在这里使用
reduce()很合理。JSON 嵌套很深:user → profile → address → city。与其手动串联查找,不如将路径表示为键的列表。然后使用reduce(operator.getitem, path, data)来遍历。这样代码更通用、可读、可复用。数据处理管道
Reduce 可以通过将数据传递给一系列转换步骤来驱动数据处理管道。每个函数处理一个步骤,按顺序应用便形成管道。以下是一个在将文本输入 NLP 模型之前进行预处理的玩具管道。
复杂应用中的错误处理
回到嵌套 JSON 示例。目前,直接调用
reduce(operator.getitem, …)会在缺少键或遇到非字典时抛出KeyError或TypeError。为提高安全性,可以定义一个辅助函数,在 try/except 中包装operator.getitem,当出错时返回默认值。以下是一个可能的辅助函数。
现在,将示例查找改为使用我们的新函数,而不是未包装的
reduce()结合 map() 和 filter() 的多步数据转换
您可以将
reduce()与其他函数式工具(如map()和filter())结合使用,以构建多步数据转换。以下是我们先前 NLP 预处理管道的函数式写法。转换步骤包括:
“The quick brown fox is Jumping over a log” → “the quick brown fox is jumping over a log”
“quick brown fox jumping log” → “quick brown fox jump log”
若要进一步探索函数式编程与向量化理念,推荐阅读以下 DataCamp 文章。
与现代 Python 生态的集成
要用好
reduce(),了解它在现代 Python 生态中的定位很有帮助。下面看看它如何与 NumPy 和 pandas 配合、如何支撑并行与分布式系统,以及如何与静态分析器等现代工具协同。numpy 与 pandas
NumPy 与 pandas 基于优化的 C 代码运行,因此不要用
reduce()去重复它们的功能。不过,reduce()适用于具有动态步骤的管道。例如,您可以在数组上组合多个 NumPy 变换。并行与分布式计算框架
Reduce 是并行与分布式计算框架的核心。这些系统通过将数据集分区、并行处理各分区、然后将部分结果合并为一个答案来工作。“合并”这一步就是归约。
要让归约在集群中正确工作,请确保满足以下条件。
如果这些性质不成立,归约要么变慢(因为无法安全并行),要么不正确(因为结果依赖于求值顺序)。
该过程需要可结合的操作与明确的幺元。否则,您会得到缓慢的代码或错误的结果。
静态分析工具
静态分析器(如 mypy、Pyright、ruff 或 bandit)是在不运行代码的情况下检查代码的工具。这些工具可以捕获缺陷、强制执行风格规则,并检查类型正确性。
静态分析器在处理
reduce()时会遇到困难。在折叠过程中,累加器类型可能与元素类型不同,类型推断会变得混乱。请看以下代码。
尽管这段代码运行良好,静态分析器仍可能提出以下疑问。
为让人和分析器更易理解,请添加类型注解。
现在,归约器明确展示了
List[str]。str。list[str]。借助这些注解,静态分析器可以更严格地检查代码。
结语
Reduce 源自函数式编程,将集合折叠为单个结果是其核心思想。尽管它在 Python 中不再是一等工具,但仍有用武之地。当您需要灵活的管道、自定义折叠,或无法恰当地映射到现有函数的操作时,
reduce()是一把强大的工具。谨慎使用时,它能与更广泛的 Python 生态良好集成,并在合适的场景中保持实用性。以下是一些您可能会觉得有用的 Python 相关链接。