在学习 Python 的过程中,我们常常会听到一个略显抽象的说法,“函数是一等公民”。初听时,我也觉得这个概念有些模糊,直到接触了 mapfilter 这类函数,才逐渐体会到其设计的精妙之处。本文我将重新梳理一下自己的思考过程,探讨到底什么是高阶函数,以及如何理解它。

方法大意

首先需要明确,所谓“一等公民”,指的是函数在 Python 中的地位和整数、列表等普通对象是完全一样的。这意味着,我们可以将一个函数赋值给变量,可以把它作为另一个函数的参数,也可以让一个函数返回另一个函数。

一旦我们接受了这个设定,高阶函数(Higher-Order Function)的定义就变得水到渠成:一个函数如果满足“接受函数作为参数”或“返回一个函数作为结果”这两个条件中的至少一个,那它就是高阶函数。Python 内置的 map, filter, reduce, sorted 等,正是因为它们都接受一个函数作为核心参数来处理数据,从而成为了高阶函数的经典范例。

基本功:mapfilter

作为最基础的例子,我们先来看看 mapfilter

map 的作用是将一个函数(操作)施加于一个序列中的每一个元素,并将所有结果打包成一个迭代器返回。例如,要计算一个列表中每个数字的平方,传统的写法可能是用 for 循环,但使用 map 会显得更为“声明式”:

numbers = [1, 2, 3, 4]
# 此处的 lambda x: x * x 就是我们传入的函数
squared_iterator = map(lambda x: x * x, numbers)
 
# map 返回的是一个迭代器,需要转换为列表才能看到完整内容
squared_list = list(squared_iterator)
print(squared_list)  # 输出: [1, 4, 9, 16]

filter 的思路与 map 类似,但它的任务是“筛选”。它接收一个返回布尔值的函数,用这个函数去检验序列中的每一个元素,只保留那些能使函数返回 True 的元素。同样,它返回的也是一个迭代器。比如,从一个列表中筛选出所有偶数:

numbers = [1, 2, 3, 4, 5, 6]
# lambda x % 2 == 0 这个函数充当了“筛选标准”
even_iterator = filter(lambda x: x % 2 == 0, numbers)
 
even_list = list(even_iterator)
print(even_list)  # 输出: [2, 4, 6]

reduce

相比 mapfilter 的“一对一”处理模式,reduce 的心智负担要稍重一些。它的作用是将一个接收两个参数的函数,以累积的方式作用于整个序列,最终将序列“规约”成一个单一的值。

这个过程有点像滚雪球:先用序列的前两个元素计算一次,得到的结果再和第三个元素计算,如此往复,直到处理完所有元素。需要注意的是,自 Python 3 起,reduce 被移出了内建函数,需要从 functools 模块中导入。

我们用它来计算一个列表中所有元素的和:

from functools import reduce
 
numbers = [1, 2, 3, 4]
# 这里的 lambda x, y: x + y 函数描述了“累积”的规则
# 其内部计算过程相当于 (((1+2)+3)+4)
sum_of_numbers = reduce(lambda x, y: x + y, numbers)
 
print(sum_of_numbers) # 输出: 10

sorted 中的 key 参数

sorted 函数本身只是一个排序工具,但其 key 参数的设计,可以说是对高阶函数思想的绝佳体现。它允许我们传入一个函数,这个函数定义了排序的“依据”。

这实现了一种优雅的“关注点分离”:sorted 函数本身只负责“排序”这个行为,而“按什么规则排序”这个具体的逻辑,则由我们传入的 key 函数来决定。例如,我们有一个包含元组的列表,希望根据每个元组的第二个元素进行排序:

pairs = [(1, 'c'), (3, 'a'), (2, 'b')]
# key 参数接收的函数,告诉 sorted:“请关注每个元素的第二个值来排序”
sorted_pairs = sorted(pairs, key=lambda x: x[1])
 
print(sorted_pairs) # 输出: [(3, 'a'), (2, 'b'), (1, 'c')]

延伸

行文至此,我其实感觉 列表推导式某种程度上在python中是一个更好的选择。mapfilter 的功能,似乎总能用列表推导式以一种更紧凑的形式实现。比如,list(map(lambda x: x*x, numbers)) 完全等价于 [x*x for x in numbers]

参考文献