函数式编程(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. 内置高阶函数:map、filter、reduce
它们体现了典型的函数式思想,但在 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.reduce 和 operator
累积运算可以用 reduce 组合。
from functools import reduce
print(reduce(lambda a, b: a * b, [1, 2, 3, 4])) # 24
4. 第三方函数式库
像 toolz、fn.py 等提供更丰富的函数式工具,如管道操作符、柯里化等,但日常开发中不建议过度使用,Python 社区更偏好简单直观的写法。
Python 中函数式编程的定位
- 数据处理流水线非常适合函数式风格,比如用 Pandas 的链式调用,或者一层层
map/filter转换数据。 - 多线程/异步场景中,无副作用的纯函数和不可变数据能减少竞态条件。
- 不要为了函数式而函数式。如果循环加几个 if-else 更清晰,那就用循环。Python 的核心原则是可读性优先。
函数式编程在 Python 里不是教条,而是一种可选的思维方式。理解并合理运用这三点,能让你在特定场景下写出更健壮、更干净的代码。