functools.lru_cache搭配装饰器报cache_info属性错误及缓存差异求解
问题原因分析
1. 两个函数缓存行为差异的原因
装饰器的执行顺序是从下往上叠加,@format_args @functools.lru_cache def slow_function_1等价于slow_function_1 = format_args(functools.lru_cache(slow_function_1))。运行时传入的列表参数会先被format_args转为可哈希的元组,再传给lru_cache包装的函数,参数满足可哈希要求因此可以正常缓存。
而slow_function_2的缓存失效是因为formatted_cache的实现逻辑错误:原代码中每次调用slow_function_2时,都会执行functools.lru_cache()(func)新建一个全新的lru_cache包装实例,每个实例的缓存空间独立,每次调用都相当于用全新的缓存实例执行函数,自然无法复用之前的缓存结果。
2. AttributeError报错的原因
你自定义的两个装饰器返回的都是内部的inner函数,默认情况下inner不会继承被包装函数的任何属性,lru_cache附加的cache_info、cache_clear等方法都被外层的inner屏蔽了,所以调用时会提示找不到对应属性。
修复方案
需要修改两个自定义装饰器:
- 用
functools.wraps透传被包装函数的属性 - 调整
formatted_cache的实现,在装饰器加载时就初始化lru_cache实例,不要每次调用都新建
修改后的可运行代码如下:
import functools import time def format_args(func): # 透传被包装函数的属性 @functools.wraps(func) def inner(*args, **kwargs): formatted_args = [tuple(x) if type(x) == list else x for x in args] return func(*formatted_args, **kwargs) return inner def formatted_cache(func): # 装饰器加载时就初始化lru_cache,全局仅创建一次 cached_func = functools.lru_cache()(func) @functools.wraps(func) def inner(*args, **kwargs): formatted_args = [tuple(x) if type(x) == list else x for x in args] # 复用同一个缓存实例 return cached_func(*formatted_args, **kwargs) # 显式绑定lru_cache的方法供外部调用 inner.cache_info = cached_func.cache_info inner.cache_clear = cached_func.cache_clear return inner @format_args @functools.lru_cache def slow_function_1(a: list, b: bool): time.sleep(1) print("executing slow function 1") return sum(a) @formatted_cache def slow_function_2(a: list, b: bool): time.sleep(1) print("executing slow function 2") return functools.reduce((lambda x, y: x*y), a) example_list = [1,2,3,4,5,6,7,8,9,10,11,12] example_bool = True slow_function_1(example_list, example_bool) print(slow_function_1.cache_info()) slow_function_1(example_list, example_bool) print(slow_function_1.cache_info()) slow_function_2(example_list, example_bool) print(slow_function_2.cache_info()) slow_function_2(example_list, example_bool) print(slow_function_2.cache_info())
运行后两个函数都只会执行1次,第二次调用直接命中缓存,cache_info也可以正常输出缓存命中统计信息。
内容的提问来源于stack exchange,提问作者cadeaudecheval
相关产品推荐
相关产品推荐

