人人都会AI编程

28.4 函数式编程:纯函数、不可变数据、函数组合

更新时间:2026-07-12

函数式编程(Functional Programming)不是 Python 的“主赛道”,但 Python 提供了足够的支持,让我们在合适的场景下采用这种范式,写出更简洁、更安全、更易测试的代码。

纯函数

纯函数是指同时满足两个条件的函数:

  • 相同的输入永远产生相同的输出(不依赖外部可变状态)。
  • 执行过程没有副作用(不修改外部变量、不写文件、不打印日志等)。

为什么有用?

  • 纯函数的行为完全由参数决定,因此测试简单,不需要 Mock 外部依赖。
  • 因为无副作用,可以放心地复用、并行调用,不会互相干扰。

Python 中的示例

# 纯函数:依赖和影响都仅限于参数和返回值
def add(a, b):
    return a + b

# 非纯函数:依赖外部变量 counter,并且修改了它
counter = 0
def increment():
    global counter
    counter += 1
    return counter

在实际代码中,把核心计算逻辑写成纯函数,把带有副作用的操作(如数据库写入、日志打印)隔离在边界部分,可以极大改善代码的可测试性和可维护性。

不可变数据

函数式编程倾向于使用不可变对象,即创建后就不能修改状态的数据。这避免了多线程或复杂流程中因共享可变状态带来的意外错误。

Python 中的不可变类型

  • 数字(int, float)、字符串(str)、元组(tuple)都是内置的不可变类型。
  • frozenset 是不可变集合。
  • 对于自定义对象,可以使用 dataclass(frozen=True)namedtuple 创建不可变数据承载类。

实用建议

  • 在处理数据转换流水线时,尽量返回新对象而不是修改原对象。例如用列表推导式生成新列表,而不是在原列表上 append
  • 如果需要类似“修改”的操作,实际上创建新对象,比如字符串拼接、元组连接。
# 好:返回新对象
def add_to_list(lst, item):
    return lst + [item]

# 差:修改了原列表
def add_to_list_mutate(lst, item):
    lst.append(item)
    return lst

函数组合

函数组合是指将多个小函数串联起来,让数据像一个流水线一样依次通过各个处理阶段,最终得到结果。Python 支持几种函数组合的常见手段:

1. 把函数作为参数传递
Python 中函数是一等公民,可以作为参数传给另一个函数。

def apply_twice(func, value):
    return func(func(value))

def add_one(x):
    return x + 1

print(apply_twice(add_one, 5))  # 7

2. 内置高阶函数:mapfilterreduce
它们体现了典型的函数式思想,但在 Python 中更推荐使用列表推导式或生成器表达式,可读性通常更高。

# 用 map 和 filter
nums = [1, 2, 3, 4, 5]
squared = map(lambda x: x**2, nums)
even_squares = filter(lambda x: x % 2 == 0, squared)
print(list(even_squares))  # [4, 16]

# 可读性更好的等价写法
print([x**2 for x in nums if x % 2 == 0])  # [4, 16]

3. 使用 functools.reduceoperator
累积运算可以用 reduce 组合。

from functools import reduce
print(reduce(lambda a, b: a * b, [1, 2, 3, 4]))  # 24

4. 第三方函数式库
toolzfn.py 等提供更丰富的函数式工具,如管道操作符、柯里化等,但日常开发中不建议过度使用,Python 社区更偏好简单直观的写法。

Python 中函数式编程的定位

  • 数据处理流水线非常适合函数式风格,比如用 Pandas 的链式调用,或者一层层 map/filter 转换数据。
  • 多线程/异步场景中,无副作用的纯函数和不可变数据能减少竞态条件。
  • 不要为了函数式而函数式。如果循环加几个 if-else 更清晰,那就用循环。Python 的核心原则是可读性优先

函数式编程在 Python 里不是教条,而是一种可选的思维方式。理解并合理运用这三点,能让你在特定场景下写出更健壮、更干净的代码。