学到这你大概已经能写"命令式"程序了:定义几个函数、调来调去、用 if/for 控制流程。
但 CS61A 真正的第一个分水岭在这一篇——当你意识到"函数"可以像数字一样被传来传去、被装进盒子、被工厂批量生产,"编程"这件事的维度就升了一级。
这种能"吃函数、拉函数"的函数,叫高阶函数(higher-order function)。它是后面装饰器、回调、甚至整个函数式编程的地基。

一、什么是"一等公民":函数不是语法糖,是值

是什么:在 Python 里,函数和数字、字符串一样,是货真价实的对象(object)。这意味着函数可以:被赋值给变量、当参数传给别的函、作为返回值、存进容器。具备这种待遇的,叫一等公民(first-class citizen)

1
2
3
4
5
6
7
def square(x):
return x * x

f = square # 函数赋值给变量(没加括号,不是调用!)
print(f(4)) # 16
print(type(f)) # <class 'function'>
print(square) # <function square at 0x...>

输出:

1
2
3
16
<class 'function'>
<function square at 0x...>

注意 f = square 后面没有括号——这把"函数对象本身"交给了 f,而不是把调用结果交出去。很多初学者在这里栽:分不清"函数"和"函数的返回值"。

函数名加括号才是调用,不加括号是引用。下面这行 bug 极隐蔽:

1
2
3
4
5
6
buttons = []
for i in range(3):
buttons.append(lambda: print(i)) # 想存"三个不同动作"
buttons[0]() # 你以为打 0,实际打 2
buttons[1]() # 2
buttons[2]() # 2

为什么三个都打 2?因为三个 lambda 共享同一个变量 i,等它们真正执行时,循环早跑完、i 已是 2。这叫闭包的延迟绑定(late binding),我们下面专门拆。

本质一句话:函数是一等公民 = 它能像普通值一样被传、被存、被返回;分清"加括号调用"和"不加括号引用"是第一道坎。

二、把函数当参数:自己造一遍 map / filter

是什么:高阶函数最经典的三种用法就是 map(映射)、filter(过滤)、reduce(归约)。Python 内置了前两个,但自己写一遍才能真正理解"函数当参数"意味着什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def my_map(fn, seq):
result = []
for item in seq:
result.append(fn(item))
return result

def my_filter(pred, seq):
result = []
for item in seq:
if pred(item):
result.append(item)
return result

nums = [1, 2, 3, 4, 5]
print(my_map(lambda x: x * x, nums)) # [1, 4, 9, 16, 25]
print(my_filter(lambda x: x % 2 == 0, nums)) # [2, 4]

输出:

1
2
[1, 4, 9, 16, 25]
[2, 4]

my_map 根本不在乎 fn 是平方还是开方——它只负责"把序列里每个元素喂给 fn"。把变化的部分(fn)抽出来当参数,不变的部分(遍历)留下来,这就是"策略模式"的雏形,也是高阶函数最大的价值:复用骨架、定制行为。

:别为了"显得函数式"而强行 map/filter 嵌套。当逻辑一复杂,清晰的 for 循环往往比三层 map(lambda...) 更易读。可读性是第一优先级。

本质一句话:高阶函数 = 把"变化的行为"参数化;map/filter/reduce 是这套思想最干净的三种表达。

三、lambda:一次性匿名函数

是什么lambda 用来写没有名字的小函数,语法是 lambda 参数: 表达式。它适合只在一处使用、逻辑只有一行的情况。

1
2
3
4
5
6
add = lambda a, b: a + b
print(add(3, 4)) # 7
# 完全等价于:
def add2(a, b):
return a + b
print(add2(3, 4)) # 7

坑一lambda只能是一个表达式,不能写 if/else 语句块、for、赋值等。需要多行逻辑就得老老实实 def

坑二(最重要):回到篇头那个延迟绑定。要修复循环里的 lambda,得立刻把当前值"冻"进默认参数

1
2
3
4
5
6
buttons = []
for i in range(3):
buttons.append(lambda i=i: print(i)) # 默认参数在定义时就求值,把 i 绑死
buttons[0]() # 0
buttons[1]() # 1
buttons[2]() # 2

lambda i=i: 利用"默认参数在定义时就绑定"的特性,把循环变量 i 的当时值快照进形参 i,从而切断了和外界 i 的联系。这是面试和作业里超高发的翻车点,记住它。

本质一句话lambda 是"一行的匿名函数",只能写表达式;要避坑就用默认参数冻结外层变量。

四、函数返回函数:闭包登场

是什么:高阶函数不仅能"收"函数,还能返回函数——这引出了 CS61A 最重要的概念之一:闭包(closure)。返回的函数会"记住"它定义时所在环境里的变量,哪怕那个环境早已"返回"了。

1
2
3
4
5
6
7
8
9
10
def make_adder(n):          # 返回一个"加 n"的函数
def adder(x):
return x + n # n 被封闭在 adder 里
return adder

add5 = make_adder(5)
add10 = make_adder(10)
print(add5(3)) # 8
print(add10(3)) # 13
print(add5(100)) # 105

输出:

1
2
3
8
13
105

make_adder(5) 返回的 adder 记住了 n=5make_adder(10) 记住了 n=10。这就是函数工厂——用一份模板批量造出"行为各异"的函数。结合篇一的环境模型看:adder 的帧里挂着定义时的 n,所以 add5add10 是两只独立的闭包,互不干扰。

:闭包"记住的是变量,不是值"。如果外部变量后来变了,闭包看到的是变后的:

1
2
3
4
5
6
7
8
9
def counter():
count = 0
def inc():
nonlocal count # 声明要改外层变量
count += 1
return count
return inc
c = counter()
print(c(), c(), c()) # 1 2 3

这里用 nonlocal 明确"我要改外层 count",否则 count += 1 会报 UnboundLocalErrornonlocal 是 Python 闭包里修改外层可变状态的钥匙。

本质一句话:闭包 = 返回的函数带着定义时的环境;它记的是变量引用,所以能"记住"也能"改写"外层状态(需 nonlocal)。

五、C 对照:把函数当值有多麻烦

CS61A 用 Python,但底层世界(OS、数据库)多是 C。C 里函数不是一等公民——没有"函数对象"这种东西,要传函数得靠函数指针,而且语法相当拧巴:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int square(int x) { return x * x; }

// 用 typedef 定义"指向(接收int返回int的函数)的指针"类型
typedef int (*Func)(int);

void apply(Func f, int v) {
printf("%d\n", f(v));
}

int main() {
apply(square, 5); // 输出 25
return 0;
}

对照要点:Python 原生把函数当值,随手传;C 必须用函数指针 + typedef,且没有闭包——想"记住外部变量"得自己手写结构体把状态打包传进去。这正是动态语言在"描述行为"上比 C 省力一个量级的原因。

六、常见坑速查(面试高频)

现象 修法
f = square() 多写括号 把返回值(常是 None)当函数传 去掉括号,传 square
lambda 循环延迟绑定 全拿到最后一个值 lambda x=x: 冻结
滥用 map/filter 嵌套 可读性崩塌 复杂逻辑用 for
闭包改外层变量忘 nonlocal UnboundLocalError nonlocal

小结

  • 一等公民:函数可赋值、传参、返回、入容器;分清"加括号调用"与"不加括号引用"。
  • 高阶函数:把变化的行为参数化;map/filter/reduce 是三种经典形态。
  • lambda:一行匿名函数,只写表达式;用默认参数冻结外层变量避坑。
  • 闭包:返回的函数带着定义时的环境,记的是变量引用;改外层状态需 nonlocal
  • C 对照:函数指针语法拧巴且无闭包,动态语言在"传行为"上省力得多。