functools.lru_cache 函数值缓存工具

264 阅读3分钟

functools.lru_cache是Python的标准库functools提供的一个功能强大的内置缓存装饰器,LRU代表Least Recently Used(最近最少使用),如果缓存容量满了,基于 LRU 策略,最近最少使用的条目会被移除。。它通过存储函数调用的结果来优化性能,尤其适用于那些输入输出关系固定且计算开销较大的函数。

参数解析

@lru_cache(maxsize=128, typed=False)
  • maxsize: 缓存的最大容量,也就是最多缓存多少个结果,默认值为128;超过容量时回按照最近最少使用策略淘汰旧的缓存;设置为None时表示无限制缓存,但时可能导致内存占用过多。
  • typed: 区分参数类型,如果typed=True,则会把f(3)f(3.0)当作不同的缓存条目;默认为False

常见用法

递归函数优化

如斐波那契数列、阶乘等,递归计算往往涉及大量重复工作。使用 lru_cache 可以避免重复计算,提升性能。

from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
	if n < 2:
		return n
	else:
		return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(10))
print(fibonacci.cache_info())
print(fibonacci(9))
print(fibonacci.cache_info())

运行结果:

55
CacheInfo(hits=8, misses=11, maxsize=128, currsize=11)
34
CacheInfo(hits=9, misses=11, maxsize=128, currsize=11)

第一次调用fibonacci(10),递归地计算了fibonacci(0)fibonacci(10)的所有结果,每个结果再计算完成后存储到缓存中。由于递归中多次重复调用,有8次缓存命中,有11次未命中。

第二次调用fibonacci(9),由于结果再第一次调用fibonacci(10)的时候已经被缓存,所以fibonacci(9)的结果直接从该缓存中返回;缓存的存取速度非常快(类似字典查找),每次从缓存中取数据,完全避免了递归和计算的时间消耗。;缓存命中次数增加一次(hits=8便车给hits=9)。

数据库查询缓存

当访问慢速资源(如数据库、API)时,可以将查询结果缓存,避免多次访问相同的数据。

from functools import lru_cache

@lru_cache(maxsize=32)
def get_user_info(user_id):
    # 假设这是一个耗时的数据库查询
    print(f"Fetching data for user_id={user_id}")
    return {"id": user_id, "name": f"User {user_id}"}

print(get_user_info(1))  # 第一次调用会执行查询
print(get_user_info(1))  # 第二次调用直接返回缓存结果

复杂计算结果缓存

对于复杂的数学运算或数据处理,缓存可以显著减少运算时间。

其他用法

清理缓存

如果需要清空缓存,可以使用cache_clear()方法:

fibonacci.cache_clear()

获取缓存统计信息

cache_info提供了缓存的命中次数、未命中次数、当前缓存大小等统计信息。

info = fibonacci.cache_info()
print(info)  # 输出类似:CacheInfo(hits=9, misses=11, maxsize=128, currsize=11)

注意事项

  • 缓存易导致内存占用过大:对于大数据或者无界递归,使用maxsize=None会导致内存爆满。应根据实际需要设置合适的maxsize
  • 不适用于带有可变参数的函数:如果函数的参数是列表、字典等可变类型,lru_cache 无法正常工作(因为参数必须可哈希)。解决办法是将参数转换为不可变类型(如元组)。
  • 仅缓存函数结果lru_cache 只缓存函数结果,不会缓存其他副作用(如打印日志、写文件等)。

总结

functools.lru_cache 是一个轻量级、高效的缓存工具,非常适合对纯函数的结果进行缓存以提高性能。通过 maxsize 控制缓存容量,结合 cache_info()cache_clear() 提供的工具,可以在大多数场景下灵活使用。

如果需求更复杂,可以考虑引入第三方缓存工具。