Python functools 练习:缓存、偏函数与归约
functools 的重点是改造函数,而不是重写函数。你可以缓存昂贵结果、固定一个参数、把序列折叠成一个值,或者在包装可调用对象时保留元数据。
缓存纯函数
lru_cache 按调用参数保存结果。它最适合参数可哈希的纯函数。如果返回可变对象并被修改,调用方之间可能共享状态,所以应返回副本,或避免缓存可变结果。
from functools import lru_cache
@lru_cache(maxsize=None)
def square(n):
return n * n
print(square(4))
print(square(4))相同输入,复用结果
查看缓存,而不是猜测
cache_info 报告命中、未命中和当前大小。用它确认重复调用是否真的复用了结果。如果命中率一直很低,缓存可能只增加内存,没有节省计算。
from functools import lru_cache
@lru_cache(maxsize=None)
def double(n):
return n * 2
double(2)
double(2)
print(double.cache_info().hits)测量缓存的效果
用 partial 固定参数
partial 返回一个已经填入部分参数的新可调用对象,不会立刻调用函数。想保留前面的位置参数时,使用关键字参数会更清楚。
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
print(square(6))专门化的可调用对象
归约时给出明确初值
reduce 每次合并两个元素。提供初值可以让空序列的结果可预测,也让累加器的类型更明确。没有初值时,空序列会抛出 TypeError。
from functools import reduce
numbers = [1, 2, 3, 4]
print(reduce(lambda total, n: total + n, numbers, 0))带安全初值的归约
包装函数时保留元数据
返回替代函数的装饰器通常应使用 wraps。它会复制原函数的名称和文档字符串,让日志、help 输出和调试信息保持可读。
from functools import wraps
def announce(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@announce
def greet():
"""Say hello."""
return "hi"
print(greet.__name__)保留原始身份
你的练习清单
- 缓存参数可哈希的纯函数。
- 用 cache_info 检查缓存是否真的有帮助。
- 用 partial 固定参数,而不是立即调用。
- 可能遇到空输入时给 reduce 提供初值。
- 替换函数的装饰器应使用 functools.wraps。
常见问题
lru_cache 可以缓存方法吗?
可以,但 self 会成为键的一部分。要确保实例可哈希,并注意缓存不会意外保留不需要的对象。
partial 会复制函数吗?
不会。它保存原可调用对象的引用,以及要预填的参数。
什么时候 reduce 不如循环清楚?
当累积过程有多个分支或需要提前退出时。带名字的循环通常比密集的 reduce 表达式更易读。
练习 functools
12 道题,覆盖缓存、偏函数参数、reduce 和包装器元数据。
继续阅读: