专项练习 · 9 分钟阅读

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__)

保留原始身份

你的练习清单

常见问题

lru_cache 可以缓存方法吗?

可以,但 self 会成为键的一部分。要确保实例可哈希,并注意缓存不会意外保留不需要的对象。

partial 会复制函数吗?

不会。它保存原可调用对象的引用,以及要预填的参数。

什么时候 reduce 不如循环清楚?

当累积过程有多个分支或需要提前退出时。带名字的循环通常比密集的 reduce 表达式更易读。

练习 functools

12 道题,覆盖缓存、偏函数参数、reduce 和包装器元数据。

打开练习

继续阅读: