(十五)用 functools 固定参数与复用结果
1. partial 预先填入部分实参
# partial 建立一个可调用对象,记住原函数和已经填入的关键字。
from functools import partial
def format_minutes(minutes, *, unit):
return f"{minutes}{unit}"
as_minutes = partial(format_minutes, unit="分钟")
print(as_minutes(35))
print(as_minutes(35, unit="min"))输出 35分钟 与 35min。第一次调用相当于 format_minutes(35, unit="分钟");第二次在这次调用中覆盖了预填的关键字。这里固定的是实参配置,与闭包中手写一个转调用函数达到相近的目的。
2. cache 记录已经算过的调用
递归求一串数的相邻两项之和时,多个分支会请求同一个下标。先用 0、1 为起点,下一项等于前两项之和,求第 4 项:
# cache 按实参保存返回结果,再次请求同一个 n 时直接读取缓存。
from functools import cache
@cache
def sequence(n):
print("计算", n)
if n < 2:
return n
return sequence(n - 1) + sequence(n - 2)
print("第一次", sequence(4))
print("第二次", sequence(4))
# cache_info 返回命中、未命中和当前记录数等统计。
info = sequence.cache_info()
print(info.hits, info.misses, info.currsize)先出现“计算 4、3、2、1、0”,然后是“第一次 3”“第二次 3”和 3 5 5。展开 sequence(3) 时,sequence(2) 已经算出 sequence(1);再次需要它就读取缓存。同样,回到 sequence(4) 时,sequence(2) 也已经有结果。第二次从外面请求 sequence(4),再增加一次命中。
缓存把重复计算换成了保存结果。这里每个下标 0 至 4 只真正计算一次,缓存保存五项。参数参与字典式索引,因此需要可哈希对象;传入列表会出现 TypeError。缓存还会保留参数与结果的引用,给函数增加或修改外部数据后,旧实参对应的缓存不会自动重新计算。
cache 持续保留已有结果;lru_cache(maxsize=128) 则限制记录数,达到数量后移除最近最久未使用的项。可以调用被装饰函数的 cache_clear() 清空记录。文件读取、网络请求等结果会随外部状态改变的操作,要先决定何时重新获取数据,再安排缓存,而不是直接套上装饰器。
3. reduce 连续合并结果
# 从初值 1 开始,每次把累计结果与下一项交给合并函数。
from functools import reduce
result = reduce(lambda product, value: product * value, [2, 3, 4], 1)
print(result)计算顺序是 1 * 2 → 2 * 3 → 6 * 4,结果 24。合并规则较长时可以换成命名函数;普通求和直接使用 sum,阅读起来更明确。functools 工具