Python lru_cache 使用与源码解读
1. 用法说明
functools.cache
和functools.lru_cache
都是Python标准库functools
模块提供的装饰器,用于缓存函数的计算结果,以提高函数的执行效率。
举一个简单的例子:
1 | from functools import lru_cache |
其中__wrapped__
表示装饰器中原始的函数,也就是没有作用装饰器之前的裸函数。
代码输出如下:
1 | Execution time1: 0.0004 seconds |
可以看到,通过lru_cache保存factorial函数的中间结果,得到了3.5倍的加速。
通过这里例子,我们可以看到lru_cache
的使用方式,也是比较简单:
- import
lru_cache:
:from functoools import lru_cache
- 给函数添加
@lru_cache
装饰器。
通过查看源码,可以看到lru_cache
函数签名如下:
1 | def lru_cache(maxsize=128, typed=False): |
其中maxsize
参数表示缓存的最多结果数,默认是128。如果计算结果超过128,则遵循Least-recently-used (LRU)原则,将最近使用次数最少的缓存结果替换为当前的结果。如果设置maxsize=None
,则缓存无上限,但内存占用也可能会增大,使用时多观察。
typed
参数表示是否按类型缓存不同变量,即使数值一样。例如typed=True
,那么f(decimal.Decimal("3.0"))
和 f(3.0)
也会分开缓存。
2. 实际使用例子
上面只是一个玩具例子,实际代码中,lru_cache
用法还是挺多的,这里举一些实际使用例子,来更清晰地理解它的功能。
2.1 get_available_devices
1 |
|
代码地址: https://github.com/huggingface/transformers/blob/f11f57c92579aa311dbde5267bc0d8d6f2545f7b/src/transformers/utils/__init__.py#L298
这是获取所有可用 torch devices的代码,通过增加lru_cache进行缓存。
2.2 API请求缓存
1 | import requests |
2.3 读取配置
1 | from functools import lru_cache |
2.4 包含参数的资源初始化
1 | from functools import lru_cache |
3. lru_cache源码分析
lru_cache源码在CPython源码目录的Lib/functools.py
中,可以在GitHub上查看。
下面通过代码截图的方式详细分析源码。
1 | def lru_cache(maxsize=128, typed=False): |
4. lru_cache和cache的区别
functools.cache
是Python 3.9引入的新特性,作为lru_cache
的无缓存大小限制的一个alias。
具体来说,通过查看源码,可以发现cache
是lru_cache
的一个特例:
1 | def cache(user_function, /): |
而lru_cache
的函数签名如下:
1 | def lru_cache(maxsize=128, typed=False): |
因此可以看出cache=lru_cache(maxsize=None, typed=False)
。因此cache
函数有两个重要的特点:
- 缓存空间无限大,也就是说不存在缓存的字典的key值超过上限,需要替换掉那些最不常用的key的情况,可以保证所有函数都能命中,但代价是会占用更多的内存。
- typed=False,表明不同类型的具有相同类型的数值会被当作一个值来缓存。
5. 不适合的应用场景
- 返回可变对象(如列表、字典)时,缓存的是对象引用,可能导致意外修改
- 函数有副作用(如写入文件、修改全局变量)
- 参数不可哈希(如传递字典、列表等可变类型)
- 参数组合可能性无限(导致缓存无限膨胀)