pin_drop当前位置:知识文库 ❯ 图文
Python functools.lru_cache详解 - LRU缓存优化函数性能
一、lru_cache 缓存概述
functools.lru_cache 是 functools 模块中最常用的性能优化装饰器,基于 LRU(Least Recently Used,最近最少使用)算法实现函数结果的自动缓存。
当使用相同的参数再次调用被装饰的函数时,lru_cache 会直接返回缓存的结果,而无需重新计算。这在递归算法、IO 密集型操作、数据库查询等场景中可以带来显著的性能提升。
LRU 算法的核心思想是:如果数据最近被访问过,那么将来被访问的几率也更高。当缓存满时,最近最少使用的数据会被淘汰,为新数据腾出空间。
二、语法格式
代码示例
@functools.lru_cache(maxsize=128, typed=False)
三、参数详细说明
四、返回值与附加方法
lru_cache 返回一个装饰器函数。被装饰的函数会获得以下额外方法:
-
cache_info():返回缓存统计信息,包含
hits(命中次数)、misses(未命中次数)、maxsize(最大缓存大小)、currsize(当前缓存大小) -
cache_clear():清空缓存中的所有条目
五、代码示例详解
示例1:加速递归计算
斐波那契数列是展示 lru_cache 威力最经典的例子。不使用缓存的递归实现时间复杂度为 O(2^n),而使用缓存后降为 O(n)。
代码示例
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# 计算第35个斐波那契数
print(f"fib(35) = {fibonacci(35)}")
print(f"缓存信息: {fibonacci.cache_info()}")
# 输出:
# fib(35) = 9227465
# 缓存信息: CacheInfo(hits=33, misses=36, maxsize=None, currsize=36)
示例2:缓存数据库查询结果
在实际开发中,lru_cache 常用于缓存耗时的 IO 操作,如数据库查询、API 请求等。下面通过模拟数据库查询来演示。
代码示例
from functools import lru_cache
import time
# 模拟耗时的数据库查询
@lru_cache(maxsize=100)
def query_user(user_id):
"""模拟查询用户信息(耗时操作)"""
time.sleep(0.1) # 模拟IO延迟
users = {
1: {'name': 'Alice', 'age': 25},
2: {'name': 'Bob', 'age': 30},
3: {'name': 'Charlie', 'age': 28},
}
return users.get(user_id, {'name': 'Unknown', 'age': 0})
# 第一次查询(缓存未命中)
start = time.time()
user1 = query_user(1)
print(f"第1次查询: {user1}, 耗时: {time.time() - start:.4f}秒")
# 第二次查询(缓存命中)
start = time.time()
user1_again = query_user(1)
print(f"第2次查询: {user1_again}, 耗时: {time.time() - start:.6f}秒")
print(f"缓存信息: {query_user.cache_info()}")
# 输出:
# 第1次查询: {'name': 'Alice', 'age': 25}, 耗时: 0.1005秒
# 第2次查询: {'name': 'Alice', 'age': 25}, 耗时: 0.000001秒
# 缓存信息: CacheInfo(hits=1, misses=1, maxsize=100, currsize=1)
可以看到,第一次查询耗时约 0.1 秒(模拟 IO 延迟),而第二次查询几乎瞬间完成,因为结果已被缓存。
示例3:typed 参数与缓存管理
typed 参数控制是否区分参数类型。同时,我们可以使用 cache_clear() 来清空缓存。
代码示例
from functools import lru_cache
# typed=True 时,1 和 1.0 被视为不同的键
@lru_cache(maxsize=32, typed=True)
def compute(x):
print(f"计算 compute({x}, type={type(x).__name__})")
return x * 2
print(compute(1)) # 缓存未命中
print(compute(1.0)) # 缓存未命中(typed=True,类型不同)
print(compute(1)) # 缓存命中
print(f"\n缓存信息: {compute.cache_info()}")
# 清空缓存
compute.cache_clear()
print(f"清空后: {compute.cache_info()}")
# 输出:
# 计算 compute(1, type=int)
# 2
# 计算 compute(1.0, type=float)
# 2.0
# 2
#
# 缓存信息: CacheInfo(hits=1, misses=2, maxsize=32, currsize=2)
# 清空后: CacheInfo(hits=0, misses=0, maxsize=32, currsize=0)
六、实际应用场景
-
递归算法优化:斐波那契数列、动态规划等递归问题,缓存中间结果避免重复计算
-
IO 密集型操作:缓存数据库查询、API 请求、文件读取的结果,减少 IO 开销
-
计算密集型操作:缓存复杂计算结果,如图像处理、数学运算等
七、注意事项
注意1:
lru_cache使用函数参数作为缓存键,因此所有参数必须是可哈希的(hashable)。列表、字典、集合等不可哈希类型不能作为参数。
注意2:缓存会占用内存。对于返回大量数据的函数,请注意设置合理的
maxsize,避免内存溢出。
注意3:
lru_cache不适合结果会随时间变化的函数(如查询实时数据),因为缓存不会自动失效。
提示:
maxsize=None适合参数范围有限的函数;maxsize=128是一个合理的默认值;maxsize=0可用于禁用缓存(调试用)。
八、与相关方法对比
Python 中有多种方式可以实现函数缓存。以下是不同缓存方案的详细对比:
小贴士
如果你的缓存需要设置过期时间(TTL),可以考虑使用第三方库 cachetools,它提供了 TTLCache、TTLKeyCache 等带过期时间的缓存类型。如果需要更复杂的缓存策略,redis 或 memcached 也是不错的选择。
九、本章小结
-
LRU 缓存:
lru_cache基于 LRU 算法自动缓存函数结果,显著提升性能 -
参数要求:参数必须是可哈希类型,缓存会占用内存
-
缓存管理:
cache_info()查看缓存统计,cache_clear()清空缓存 -
高级用法:
typed=True区分不同类型的参数,maxsize=None不限制缓存大小
十、练习题
练习1
使用 lru_cache 实现一个高效的阶乘计算函数 factorial(n),并比较缓存命中和未命中的次数。
练习2
编写一个使用 lru_cache 缓存的函数 get_weather(city),模拟查询城市天气(用 time.sleep 模拟网络延迟),验证第二次查询同一城市时是否命中缓存。
练习3
使用 lru_cache 实现一个计算组合数 C(n, k) 的函数,利用递归公式 C(n, k) = C(n-1, k-1) + C(n-1, k),并计算 C(30, 15)。
常见问题
lru_cache 的缓存是线程安全的吗?
是的,lru_cache 内部使用了线程锁来保证缓存操作的原子性,可以在多线程环境中安全使用。多个线程同时访问同一个缓存键时,只有一个线程会执行实际计算,其他线程会等待结果。
maxsize 应该设置为多少合适?
maxsize 的设置取决于具体场景。如果函数的参数组合有限且内存充足,可以设置为 None(无限缓存)。对于参数组合较多的函数,建议设置为 128、256 或 1024 等值,在缓存命中率和内存占用之间取得平衡。
如何让 lru_cache 的缓存过期?
lru_cache 本身不支持时间过期机制。如果需要缓存过期,可以:1)使用 cache_clear() 手动清空缓存;2)使用第三方库 cachetools 的 TTLCache;3)在函数内部加入时间判断逻辑,当数据过期时主动抛出异常绕过缓存。
lru_cache 可以缓存有副作用的函数吗?
不建议。lru_cache 适合纯函数(相同的输入总是产生相同的输出,且没有副作用)。如果函数有副作用(如写文件、修改全局变量),缓存会导致副作用只执行一次,后续调用直接返回缓存结果而不再执行副作用,这可能不是预期行为。
lru_cache 和 memoize 有什么区别?
memoize(记忆化)是一个通用的编程概念,指缓存函数返回结果以避免重复计算。lru_cache 是 Python 中 memoize 的一种具体实现,它额外提供了 LRU 淘汰策略来管理缓存大小。简单的 memoize 通常没有缓存大小限制。
本文涉及AI创作
内容由AI创作,请仔细甄别