在 Python 编程中,我们经常会遇到一些计算开销较大的函数,尤其是递归函数或涉及复杂逻辑的纯函数。如果这些函数被频繁调用,并且参数组合有限,那么重复计算相同输入的结果将造成不必要的性能浪费。Python 标准库中的 functools.lru_cache 装饰器正是为解决这一问题而设计的。
本文将详细介绍 lru_cache 的基本用法、参数配置、适用场景以及使用时需要注意的事项。
什么是 lru_cache?
lru_cache 是 functools 模块提供的一个装饰器,用于自动缓存函数的返回值。其名称中的 “LRU” 代表 “Least Recently Used”(最近最少使用),即当缓存达到容量上限时,会自动移除最久未被访问的条目,以腾出空间存储新的结果。
通过缓存机制,lru_cache 能显著减少重复计算,尤其适用于具有重叠子问题的递归算法(如斐波那契数列、动态规划等)。
基本用法
使用 lru_cache 非常简单,只需在目标函数前加上装饰器即可:
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n – 1) + fibonacci(n – 2)
print(fibonacci(30)) # 首次调用会进行计算
print(fibonacci(30)) # 第二次调用直接从缓存返回结果
在上述例子中,fibonacci(30) 的计算原本具有指数级时间复杂度,但借助 lru_cache 后,每个 n 值仅计算一次,整体时间复杂度降为线性。
参数详解
lru_cache 接受两个可选参数:
maxsize
指定缓存的最大条目数。默认值为 128。当缓存满时,最久未使用的条目将被清除。
- 若设为 None,则缓存无大小限制(慎用,可能导致内存泄漏)。
- 官方建议将 maxsize 设置为 2 的幂(如 128、256),因为内部实现对此做了优化。
typed
布尔值,默认为 False。若设为 True,则不同类型的参数会被视为不同的缓存键。
例如:
@lru_cache(maxsize=32, typed=True)
def square(x):
print(f"计算 {x} 的平方")
return x * x
square(3) # 打印并计算
square(3.0) # 再次打印并计算(因类型不同)
若 typed=False(默认),则 3 和 3.0 被视为相同参数,第二次调用将命中缓存。
查看与管理缓存状态
被 lru_cache 装饰的函数提供了两个实用方法:
- cache_info():返回缓存的统计信息,包括命中次数(hits)、未命中次数(misses)、最大容量和当前缓存大小。
- cache_clear():清空当前缓存。
示例:
from functools import lru_cache
@lru_cache(maxsize=10)
def compute(x):
return x ** 2
compute(5)
compute(5)
print(compute.cache_info())
# 输出:CacheInfo(hits=1, misses=1, maxsize=10, currsize=1)
compute.cache_clear()
print(compute.cache_info())
# 输出:CacheInfo(hits=0, misses=0, maxsize=10, currsize=0)
这些方法在调试和性能分析时非常有用。
适用场景
lru_cache 最适合以下情况:
注意事项
尽管 lru_cache 功能强大,但使用时需注意以下几点:
- 参数必须可哈希:因为缓存底层使用字典实现,所有函数参数必须是可哈希类型(如 int、str、tuple)。不能使用 list、dict 等不可哈希类型作为参数。
- 避免用于有副作用的函数:如果函数依赖外部状态或产生副作用(如写文件、修改对象),缓存可能导致逻辑错误。
- 内存消耗:若 maxsize 设置过大或设为 None,在高频调用下可能占用大量内存。
- 线程安全:在 CPython 中,lru_cache 是线程安全的,但在其他 Python 实现中需谨慎验证。
总结
functools.lru_cache 是 Python 中一个轻量级但高效的缓存工具。通过简单的装饰器语法,开发者可以轻松为函数添加记忆化(memoization)能力,显著提升程序性能。合理使用 maxsize 和 typed 参数,并结合 cache_info() 进行监控,能够帮助我们在实际项目中更高效地利用这一特性。
在编写性能敏感的代码时,不妨考虑是否可以通过 lru_cache 来避免重复计算——有时候,一行装饰器就能带来数量级的性能提升。





