人人都会AI编程

18.2 functools:偏函数、缓存、高阶函数工具

更新时间:2026-07-12

functools 是 Python 标准库中专门处理高阶函数(操作或返回函数的函数)的模块。它提供了一系列工具,让你能更灵活地创建、修改和优化函数。日常开发中最常用的是偏函数、函数缓存和装饰器相关工具。

偏函数 partial:给函数“预填”参数

functools.partial 可以固定一个函数的部分参数,生成一个新函数。这在你需要多次调用同一个函数、且某些参数基本不变时特别有用,能减少重复代码,也让接口更清晰。

from functools import partial

# 原始函数:计算 x 的 n 次幂
def power(base, exponent):
    return base ** exponent

# 用 partial 固定 exponent=2,生成“平方”函数
square = partial(power, exponent=2)
cube = partial(power, exponent=3)

print(square(5))   # 25
print(cube(5))     # 125

典型场景

  • 回调函数参数适配:比如 GUI 按钮点击时回调只有一个参数,但你需要传递额外信息,就可以用 partial 绑定。
  • 简化复杂接口:当某个库函数的参数很多,而你的业务中大部分参数都是固定值,可以创建一个 partial 版本,只暴露业务关心的参数。
  • 配合 map/sorted:比如对列表排序时指定一个固定的 key 提取函数,可以用 partial 快速生成。

函数缓存 lru_cache / cache:避免重复计算

当某个函数被频繁调用、计算代价较高、且输入相同输出也相同时,缓存它的返回值可以显著提升性能。functools.lru_cache 提供了一个基于 LRU(最近最少使用)策略的缓存。

from functools import lru_cache

# 一个计算斐波那契数的递归函数(效率极低,因为重复计算)
# 加上缓存后性能暴涨
@lru_cache(maxsize=128)   # maxsize 控制缓存大小,None 表示不限制
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

print(fib(50))  # 不加缓存几乎跑不动,加了瞬间出结果

重要参数与变体

  • maxsize:缓存最大条目数。设为 None 则缓存无限增长(Python 3.8+ 可以使用 @cache 装饰器,相当于 @lru_cache(maxsize=None))。
  • typed:设为 True 时,不同参数类型的相同值会分开缓存(例如 f(3)f(3.0) 不共享缓存)。
  • 缓存穿透与失效:缓存是内存中的,进程结束就消失。如果需要持久化缓存,得另找方案(如 diskcache)。

实用建议

  • 缓存纯函数(相同输入永远返回相同输出,无副作用)效果最好,比如复杂计算、数据库查询结果(当数据不常变时)。
  • 不要滥用:缓存会占用额外内存,且对于参数组合极多、每次调用参数几乎都不同的函数,缓存反而增加开销。
  • 可以用 func.cache_info() 查看缓存命中率,帮助调优。

高阶函数工具:reducewraps

虽然 mapfilter 已经内置,但 reduce(逐元素累积值)被放到了 functools 里,体现了“少用”的理念,但该用的时候依然方便。

from functools import reduce

# 计算列表中所有数字的乘积
nums = [1, 2, 3, 4]
product = reduce(lambda x, y: x*y, nums)  # 24
# 也可以给初始值
product_with_initial = reduce(lambda x, y: x*y, nums, 10)  # 240

不过,在实际开发中,能不用 reduce 就尽量用内置函数(如 summath.prod)或显式的循环,因为可读性更好。只有在确实需要自定义累积逻辑时才搬它。

装饰器必备:wraps

写装饰器时,如果不加 functools.wraps,原始函数的元信息(namedoc 等)会被装饰器内部函数覆盖。这会导致调试困难和不好看的日志输出。因此写装饰器,第一个要加的就是 @wraps(func)

from functools import wraps

def log(func):
    @wraps(func)        # 保留原始函数的名字和文档
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log
def add(a, b):
    """返回 a + b"""
    return a + b

print(add.__name__)  # add,如果没有 @wraps 会是 wrapper
print(add.__doc__)   # 返回 a + b

其他值得一提的工具

  • total_ordering:用在自定义类上,只需定义 eq 和另外一个魔法方法(如 lt),它会自动补全所有比较方法(legtge)。减少样板代码。
  • singledispatch:实现单分派的泛型函数,可以根据第一个参数的类型,调用不同的函数实现。用于编写对不同数据类型有不同处理逻辑的库时很干净。
  • cached_property:在类中,将方法变成属性,计算结果只计算一次并缓存为实例属性。常用来替代惰性计算场景下反复调用的计算属性。

小结functools 提供的工具虽然不多,但每个都很实用。日常高频出现的是 partial 简化函数调用、lru_cache 优化耗时函数、wraps 写出地道的装饰器。掌握它们能让你的 Python 代码更简洁、更高效。