Python函数式编程:高阶函数、闭包、匿名函数、装饰器与偏函数

在学习完 Python 的基础语法和高级特性之后,接下来要进入一个既“抽象”又“实用”的主题:函数式编程。很多人第一次接触这个词时,会觉得它像一套理论概念,离日常开发比较远。但实际上,在 Python 中,函数式编程并不是一种只能写学术代码的技巧,而是大量真实开发场景中的常用能力。

比如:

  • map()filter() 快速处理数据序列;
  • sorted(key=...) 自定义排序逻辑;
  • 用闭包封装状态;
  • 用装饰器增强函数能力;
  • 用偏函数简化重复调用。

这些能力的共同基础是:在 Python 中,函数本身就是对象,可以像数据一样被传递、返回和组合。本文就围绕这一点,把函数式编程里最核心、最常用的内容系统讲清楚。

一、什么是函数式编程?

函数式编程(Functional Programming)并不等于“只准写函数、不准写类”。

更准确地说,它是一种编程思路,强调:

  1. 函数是一等公民:函数可以赋值给变量、作为参数传入、作为结果返回;
  2. 尽量把逻辑拆成小而清晰的函数
  3. 通过组合函数来表达数据处理过程
  4. 减少不必要的副作用,让代码更容易理解和测试。

Python 并不是一门“纯函数式语言”,它更像是一门多范式语言

  • 你可以写面向过程代码;
  • 你可以写面向对象代码;
  • 你也可以在很多场景里使用函数式风格。

所以,学习函数式编程的重点不是“必须全部这么写”,而是理解:什么时候这种方式更优雅、更简洁、更适合表达问题。


二、函数也是对象:高阶函数的起点

Python 里最重要的一条规则之一就是:函数本身也是对象

2.1 函数名其实也是变量

先看一个简单例子:

1
print(abs(-10))

这里调用了内置函数 abs(),输出绝对值。

但如果只写函数名,不加括号:

1
print(abs)

输出的不是计算结果,而是函数对象本身。

这说明:

  • abs(-10) 是函数调用
  • abs 是函数对象

既然函数是对象,就可以赋值给变量:

1
2
f = abs
print(f(-10)) # 10

这说明变量 f 现在也指向了这个函数。

2.2 高阶函数的定义

如果一个函数可以接收另一个函数作为参数,或者返回一个函数,那么它就属于高阶函数

例如:

1
2
3
4
def add(x, y, func):
return func(x) + func(y)

print(add(-5, 6, abs)) # 11

这里:

  • add() 接收了一个函数参数 func
  • 调用时传入的是 abs
  • 最终执行的是 abs(-5) + abs(6)

这就是最典型的高阶函数。

2.3 为什么高阶函数有价值?

因为它把“运算规则”抽象了出来。

例如上面的 add() 并不关心你到底想如何处理 xy,它只要求你传入一个“处理函数”。这样逻辑就变得更灵活、更可复用。

这也是函数式编程里最核心的思想之一:

把“做什么”交给函数参数,而不是把所有逻辑写死在函数内部。


三、map():把一个函数作用到整个序列

函数式编程中一个很常见的需求是:把同一种处理规则应用到一批数据上map() 就是用来做这件事的。

3.1 map() 的基本用法
1
2
3
4
5
6
def square(x):
return x * x

result = map(square, [1, 2, 3, 4, 5])
print(result)
print(list(result))

输出中 result 本身是一个迭代器,真正转成列表后才会看到:

1
[1, 4, 9, 16, 25]
3.2 map() 的语义很清晰

你可以把它理解为:

  • 对序列中的每个元素执行一次相同函数;
  • 生成一个新的结果序列;
  • 原始数据不被原地修改。

比如把数字转字符串:

1
print(list(map(str, [1, 2, 3, 4, 5])))

输出:

1
['1', '2', '3', '4', '5']
3.3 map() 和普通循环的对比

普通写法:

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

函数式写法:

1
result = list(map(square, [1, 2, 3, 4, 5]))

两种都可以。

区别在于:

  • 循环更直观,适合复杂逻辑;
  • map() 更强调“把某个函数批量作用到整个序列”。
3.4 什么时候适合用 map()

适合:

  • 单步转换逻辑明确;
  • 整个过程像“映射”;
  • 想表达“我不是在写循环,我是在做批量转换”。

不适合:

  • 逻辑太复杂;
  • 每个元素处理时还有很多分支和副作用。

在 Python 中,很多时候列表生成式也能替代 map(),比如:

1
[x * x for x in [1, 2, 3, 4, 5]]

通常可读性还会更高。


四、reduce():把一个序列压缩成一个结果

如果说 map() 是“逐个转换”,那么 reduce() 更像是“逐步汇总”。

4.1 reduce() 的基本思想

reduce() 会把一个二元函数重复作用到序列上,把多个元素压缩成一个最终值。

例如求和:

1
2
3
4
5
6
from functools import reduce

def add(x, y):
return x + y

print(reduce(add, [1, 3, 5, 7, 9])) # 25

它的执行过程近似于:

1
add(add(add(add(1, 3), 5), 7), 9)
4.2 reduce() 的典型场景

例如把数字序列拼成一个整数:

1
2
3
4
5
6
from functools import reduce

def fn(x, y):
return x * 10 + y

print(reduce(fn, [1, 3, 5, 7, 9])) # 13579
4.3 配合 map() 做字符串转整数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from functools import reduce

DIGITS = {
'0': 0, '1': 1, '2': 2, '3': 3, '4': 4,
'5': 5, '6': 6, '7': 7, '8': 8, '9': 9,
}

def char2num(ch):
return DIGITS[ch]

def combine(x, y):
return x * 10 + y

def str2int(s):
return reduce(combine, map(char2num, s))

print(str2int('13579')) # 13579

这个例子很经典,因为它展示了函数式编程最典型的组合方式:

  • map():把字符转换成数字;
  • reduce():把数字列表汇总成整数。
4.4 reduce() 的优点与局限

优点:

  • 表达“汇总成一个结果”的语义很强;
  • 很适合做累积计算、聚合计算。

局限:

  • 对初学者可读性不如显式循环;
  • 有些场景 Python 已经有更直接的内置函数,如 sum()max()min()

例如求和时:

1
sum([1, 2, 3, 4])

通常就比 reduce() 更直接。

4.5 什么时候该慎用 reduce()

如果你的同事看到代码后第一反应是“这段逻辑到底怎么运行的”,那就可能不如展开写循环。

函数式编程不是为了炫技,清晰始终优先于抽象


五、filter():从序列中筛选符合条件的元素

filter() 用于做过滤。

5.1 filter() 的基本用法
1
2
3
4
5
def is_odd(n):
return n % 2 == 1

result = filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])
print(list(result))

输出:

1
[1, 5, 9, 15]

这里的含义就是:

  • 对每个元素调用 is_odd()
  • 返回值为 True 的保留;
  • 返回值为 False 的丢弃。
5.2 过滤空字符串和空白内容
1
2
3
4
def not_empty(s):
return s and s.strip()

print(list(filter(not_empty, ['A', '', 'B', None, 'C', ' '])))

输出:

1
['A', 'B', 'C']

这是很实用的场景,例如清理用户输入、CSV 内容或日志字段。

5.3 filter() 返回的也是惰性序列

map() 一样,filter() 返回的不是列表,而是一个迭代器。

所以通常你会看到:

1
list(filter(...))

或者直接在 for 循环中消费它。

5.4 用 filter() 实现更有意思的筛选

比如筛选回文数:

1
2
3
4
5
6
def is_palindrome(n):
s = str(n)
return s == s[::-1]

result = list(filter(is_palindrome, range(1, 200)))
print(result)
5.5 filter() 和列表生成式的对比

filter() 写法:

1
list(filter(is_odd, numbers))

列表生成式写法:

1
[x for x in numbers if x % 2 == 1]

在 Python 社区里,后一种其实更常见,因为更直观。

所以这里的重点不是“必须使用 filter()”,而是理解:

  • 你可以把“筛选规则”抽成函数;
  • 你可以把“遍历”和“筛选逻辑”分离开。

六、sorted():排序也是一种高阶函数应用

排序是编程中非常常见的需求,而 sorted() 之所以重要,是因为它可以接收一个函数作为 key 参数,从而自定义排序规则。

6.1 基本排序
1
print(sorted([36, 5, -12, 9, -21]))

输出:

1
[-21, -12, 5, 9, 36]
6.2 自定义排序规则:按绝对值排序
1
print(sorted([36, 5, -12, 9, -21], key=abs))

输出:

1
[5, 9, -12, -21, 36]

这里 key=abs 的意思是:

  • 每个元素先经过 abs() 转换;
  • 排序时比较转换后的结果;
  • 最终返回原元素的排列。
6.3 字符串排序时忽略大小写
1
2
words = ['bob', 'about', 'Zoo', 'Credit']
print(sorted(words, key=str.lower))

输出:

1
['about', 'bob', 'Credit', 'Zoo']
6.4 反向排序
1
print(sorted(words, key=str.lower, reverse=True))
6.5 对复杂对象排序

例如有一组学生数据:

1
students = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]

按名字排序:

1
2
3
4
def by_name(item):
return item[0]

print(sorted(students, key=by_name))

按成绩从高到低排序:

1
2
3
4
def by_score(item):
return item[1]

print(sorted(students, key=by_score, reverse=True))
6.6 为什么说 sorted() 是高阶函数?

因为它把“排序规则”抽象成了一个函数。

你不需要重写排序算法本身,只需要告诉 sorted()

  • 每个元素拿什么值来比较;
  • 是否倒序。

这就是高阶函数强大的地方:核心逻辑稳定,变化部分通过函数传入。


七、返回函数与闭包(Closure)

函数式编程不只有“传函数进去”,还包括“把函数返回出来”。

7.1 返回函数的基本形式
1
2
3
4
5
6
7
def lazy_sum(*args):
def do_sum():
total = 0
for n in args:
total += n
return total
return do_sum

调用时:

1
2
3
f = lazy_sum(1, 3, 5, 7, 9)
print(f) # 一个函数对象
print(f()) # 25

这里 lazy_sum() 没有立刻算出结果,而是返回了一个函数,等你真正调用时再计算。

7.2 什么是闭包?

上面的 do_sum() 在内部使用了外层函数 lazy_sum() 的变量 args。即使 lazy_sum() 已经执行结束,这些变量依然被保存在返回的函数里。

这就是闭包:

内层函数引用了外层函数的局部变量,并随着函数一起被“带出去”。

7.3 每次调用都会返回新的函数对象
1
2
3
4
f1 = lazy_sum(1, 3, 5)
f2 = lazy_sum(1, 3, 5)

print(f1 == f2) # False

虽然参数相同,但它们是两个不同的函数对象。

7.4 闭包里最容易踩的坑:循环变量

来看这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
def count():
fs = []
for i in range(1, 4):
def f():
return i * i
fs.append(f)
return fs

f1, f2, f3 = count()
print(f1())
print(f2())
print(f3())

很多人以为会输出:

1
2
3
1
4
9

但实际输出是:

1
2
3
9
9
9

原因是:闭包里引用的是变量 i 本身,而不是循环当时的值。等函数真正执行时,循环已经结束了,i 最终等于 3

7.5 如何修复这个问题?

一种常见做法是再包一层,把当前值绑定进去:

1
2
3
4
5
6
7
8
9
10
def count():
def make_func(j):
def g():
return j * j
return g

fs = []
for i in range(1, 4):
fs.append(make_func(i))
return fs

这样就能正确输出 1、4、9

7.6 nonlocal:修改外层局部变量

如果闭包里不仅要读取外层变量,还要修改它,就需要 nonlocal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def create_counter():
count = 0

def counter():
nonlocal count
count += 1
return count

return counter

c = create_counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3

这个例子非常经典,它说明闭包不仅能“保存数据”,还能“封装状态”。

7.7 闭包的实际价值

闭包经常用于:

  • 延迟计算;
  • 封装私有状态;
  • 构造函数工厂;
  • 实现装饰器。

装饰器本质上就大量依赖闭包。


八、匿名函数 lambda:适合简单场景的一次性函数

有些时候,你只是临时需要一个很简单的函数,不想专门写个 def,这时就可以用匿名函数。

8.1 最基本的 lambda 写法
1
2
f = lambda x: x * x
print(f(5)) # 25

它等价于:

1
2
def f(x):
return x * x
8.2 在高阶函数中非常常用

例如:

1
print(list(map(lambda x: x * x, [1, 2, 3, 4, 5])))

再比如:

1
print(list(filter(lambda x: x % 2 == 1, range(1, 20))))

或者:

1
2
students = [('Bob', 75), ('Adam', 92), ('Lisa', 88)]
print(sorted(students, key=lambda item: item[1], reverse=True))
8.3 lambda 的限制

Python 的匿名函数只支持一个表达式,不能写多条语句。

也就是说它适合:

  • 简单映射;
  • 简单筛选;
  • 简单 key 提取;
  • 很短的小逻辑。

不适合:

  • 复杂条件;
  • 多步处理;
  • 需要注释说明的逻辑。
8.4 实战建议

如果 lambda 一眼看不懂,就别用。

例如:

1
sorted(data, key=lambda item: item['score'])

这很清晰。

但如果你写成一个非常长、嵌套复杂的表达式,那就不如单独定义函数更好维护。


九、装饰器(Decorator):在不改原函数代码的前提下增强功能

装饰器是 Python 中非常有代表性的语法能力。它经常让初学者一开始觉得抽象,但一旦理解,本质并不神秘。

9.1 为什么需要装饰器?

假设你有一个函数:

1
2
def now():
print('2026-04-24')

现在你想在调用它之前自动打印一条日志,但又不想改动原函数内容。这时就可以用装饰器。

9.2 最基础的装饰器写法
1
2
3
4
5
def log(func):
def wrapper(*args, **kwargs):
print(f'call {func.__name__}()')
return func(*args, **kwargs)
return wrapper

使用方式:

1
2
3
@log
def now():
print('2026-04-24')

调用:

1
now()

输出:

1
2
call now()
2026-04-24
9.3 @log 本质上做了什么?

这行代码:

1
2
3
@log
def now():
...

本质上等价于:

1
2
3
4
def now():
...

now = log(now)

也就是说:

  • 原始函数被传给 log()
  • log() 返回一个新函数 wrapper
  • 变量 now 最终指向这个新函数。
9.4 带参数的装饰器

有时你希望装饰器自己也接收参数,例如自定义日志文本:

1
2
3
4
5
6
7
def log(text):
def decorator(func):
def wrapper(*args, **kwargs):
print(f'{text} {func.__name__}()')
return func(*args, **kwargs)
return wrapper
return decorator

使用:

1
2
3
@log('execute')
def now():
print('2026-04-24')
9.5 functools.wraps:保留原函数元信息

如果直接写装饰器,被装饰后的函数 __name__ 往往会变成 wrapper。这会影响调试和某些依赖函数元信息的框架。

所以推荐这样写:

1
2
3
4
5
6
7
8
import functools

def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f'call {func.__name__}()')
return func(*args, **kwargs)
return wrapper
9.6 一个实用示例:统计执行时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import time
import functools


def metric(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
start = time.time()
result = fn(*args, **kwargs)
end = time.time()
print(f'{fn.__name__} executed in {(end - start) * 1000:.2f} ms')
return result
return wrapper


@metric
def slow_add(x, y):
time.sleep(0.1)
return x + y

print(slow_add(3, 5))
9.7 装饰器适合什么场景?

装饰器非常适合这些横切关注点:

  • 日志打印;
  • 执行计时;
  • 权限校验;
  • 缓存;
  • 参数检查;
  • 重试机制。

这些逻辑往往“和业务有关,但不属于业务核心本身”,装饰器很适合把它们从业务函数里分离出来。

9.8 小结:装饰器的本质是什么?

一句话总结:

装饰器本质上就是“接收函数、返回增强版函数”的高阶函数。

它大量依赖闭包,因此你会发现函数式编程的这些知识点是相互关联的。


十、偏函数(Partial Function):提前固定一部分参数

有些函数本身很通用,但你在项目中经常只会以某种固定方式调用它。这个时候,偏函数就非常有用。

10.1 经典例子:固定进制转换

int() 默认按十进制转换:

1
print(int('12345'))

但它也支持指定进制:

1
print(int('1000000', base=2))

如果你经常处理二进制字符串,每次都写 base=2 就有点重复。

10.2 自己封装一个函数
1
2
3
4
def int2(x, base=2):
return int(x, base=base)

print(int2('1000000'))
10.3 用 functools.partial() 更优雅地做同样的事
1
2
3
4
5
6
import functools

int2 = functools.partial(int, base=2)

print(int2('1000000')) # 64
print(int2('1010101')) # 85
10.4 偏函数不是“只能固定关键字参数”

还可以固定位置参数,例如:

1
2
3
4
import functools

max2 = functools.partial(max, 10)
print(max2(5, 6, 7)) # 10

这里等价于:

1
max(10, 5, 6, 7)
10.5 偏函数适用场景

偏函数适合:

  • 某个通用函数经常以固定参数形式使用;
  • 想减少重复传参;
  • 想把一部分配置提前固化。

常见例子:

  • 固定日志前缀;
  • 固定数据源配置;
  • 固定编码格式;
  • 固定类型转换规则。
10.6 偏函数和闭包的区别

它们都能“记住一些外部信息”,但思路不同:

  • 闭包:通常是你自己定义新函数并捕获变量;
  • 偏函数:是基于已有函数固定部分参数,快速生成新函数。

从工程角度讲,偏函数更适合“参数预绑定”,闭包更适合“自定义行为封装”。


十一、函数式编程在 Python 中的实际地位

学完这些内容后,你可能会问:

真实开发中,Python 会像 Lisp、Haskell 那样大量使用函数式风格吗?

答案是:不会完全那样,但会经常借用其中非常实用的部分。

11.1 Python 更强调“实用主义”

在 Python 里:

  • map()filter()reduce() 可以用;
  • 但很多时候列表生成式更直观;
  • lambda 很方便;
  • 但复杂逻辑还是建议写 def
  • 装饰器非常常用;
  • 闭包也经常见;
  • 但并不是所有场景都要“函数式化”。
11.2 真正重要的是抽象能力

学习函数式编程,不是为了把所有代码写得像数学公式,而是为了获得这些能力:

  • 能把逻辑拆分成更小、更独立的函数;
  • 能把“变化部分”抽成参数;
  • 能在合适时用函数组合来表达数据流;
  • 能理解装饰器、回调、闭包这些高级写法背后的原理。

这才是最有价值的部分。


十二、综合示例:把函数式思维串起来

下面用一个小例子,把前面几种能力串起来。

需求:

  • 输入一组名字;
  • 统一格式化成首字母大写;
  • 筛选出长度大于 3 的名字;
  • 按忽略大小写排序;
  • 打印处理耗时。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import time
import functools


def metric(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
start = time.time()
result = fn(*args, **kwargs)
end = time.time()
print(f'{fn.__name__} executed in {(end - start) * 1000:.2f} ms')
return result
return wrapper


def normalize(name):
return name[:1].upper() + name[1:].lower()


@metric
def process_names(names):
normalized = map(normalize, names)
filtered = filter(lambda x: len(x) > 3, normalized)
return sorted(filtered, key=str.lower)


names = ['adam', 'LISA', 'barT', 'tom', 'JERRY']
print(process_names(names))

输出结果会类似:

1
2
['Adam', 'Bart', 'Jerry', 'Lisa']
process_names executed in 0.08 ms

这个例子里用到了:

  • 普通函数;
  • map()
  • filter()
  • lambda
  • sorted()
  • 装饰器。

它很好地体现了函数式编程在 Python 中的“组合式表达能力”。


十三、学习建议:掌握思想,比死背函数名更重要

学习这一章时,我建议你重点掌握下面几个问题:

13.1 先理解函数为什么能被传来传去

只要理解了“函数也是对象”,很多概念都会顺下来:

  • 为什么能把函数作为参数传入;
  • 为什么能把函数返回出去;
  • 为什么装饰器成立;
  • 为什么闭包成立。
13.2 不要为了函数式而函数式

如果一个普通 for 循环最清楚,就用循环;
如果一个列表生成式更清晰,就用生成式;
如果 lambda 会让逻辑更绕,就换成普通函数。

真正好的 Python 代码,不是“看起来高级”,而是别人能快速看懂

13.3 装饰器和闭包是重点

在真实开发中:

  • map() / filter() / reduce() 是有用的;
  • 但装饰器、闭包、偏函数的价值往往更高;
  • 尤其是阅读框架源码时,装饰器会非常常见。

所以如果要划重点,我建议优先真正学透:

  1. 高阶函数;
  2. 闭包;
  3. lambda
  4. 装饰器;
  5. 偏函数。

十四、总结

这一篇我们系统学习了 Python 中函数式编程的核心能力:

  1. 高阶函数:函数可以作为参数传入,也可以作为结果返回;
  2. map():把函数批量作用于序列;
  3. reduce():把一组值逐步汇总成一个结果;
  4. filter():筛选符合条件的元素;
  5. sorted():通过 key 抽象排序规则;
  6. 闭包:返回函数并保存外层作用域的状态;
  7. lambda:快速定义简单匿名函数;
  8. 装饰器:在不改原函数代码的前提下增强能力;
  9. 偏函数:固定部分参数,生成更易调用的新函数。

如果说前面的基础语法解决的是“怎么写 Python”,高级特性解决的是“怎么写得更像 Python”,那么函数式编程这一章解决的就是:

如何把 Python 代码写得更抽象、更灵活、更易组合。

掌握这些内容后,你不仅能写出更优雅的工具函数,也能更轻松地读懂很多框架、库和高级代码风格。下一步继续学习模块、面向对象或错误处理时,这些知识都会频繁派上用场。

参考资料