使用functools.lru_cache忽略字典参数时缓存未生效问题排查
问题定位与解决方案
你的缓存未生效的核心问题出在**BlackBox类未正确实现哈希(__hash__)和相等性(__eq__)方法**,导致即使字典内容相同,每次包装生成的BlackBox实例仍被视为不同对象,lru_cache无法识别为同一缓存键。
具体问题分析
Python默认的__hash__方法基于对象的内存地址(身份)实现,而非对象内容。如果你的BlackBox类仅做了简单的字典包装,即使两个实例的字典内容完全一致,它们的哈希值也会不同,lru_cache会判定为不同参数,导致每次调用都重新执行函数。
修复步骤
1. 修正BlackBox类的哈希与相等性实现
让BlackBox的哈希和相等性基于字典的内容而非实例身份,需要将字典转换为可哈希的有序结构(比如排序后的键值对元组):
class BlackBox: def __init__(self, obj): self.obj = obj # 将字典转换为可哈希的有序结构,确保内容相同的字典生成一致的哈希基础 if isinstance(obj, dict): self._hashable = tuple(sorted(obj.items())) else: self._hashable = obj def __hash__(self): # 基于转换后的可哈希结构计算哈希值 return hash(self._hashable) def __eq__(self, other): # 仅与同类型实例比较,且比较内容而非身份 if not isinstance(other, BlackBox): return False return self._hashable == other._hashable
2. 优化装饰器的参数处理逻辑
确保装饰器先将字典参数替换为BlackBox实例,再传递给lru_cache装饰的内部函数,同时保留原函数的调用参数:
import functools def lru_cache_ignore(maxsize=None): def decorator(func): # 内部缓存函数,接收包装后的可哈希参数 @functools.lru_cache(maxsize=maxsize) def cached_func(*processed_args, **processed_kwargs): # 将BlackBox实例还原为原始字典,传递给原函数 original_args = tuple(arg.obj if isinstance(arg, BlackBox) else arg for arg in processed_args) original_kwargs = {k: v.obj if isinstance(v, BlackBox) else v for k, v in processed_kwargs.items()} return func(*original_args, **original_kwargs) # 外部包装函数,处理原始参数 def wrapped(*args, **kwargs): # 将所有字典参数替换为BlackBox实例 processed_args = tuple(BlackBox(arg) if isinstance(arg, dict) else arg for arg in args) processed_kwargs = {k: BlackBox(v) if isinstance(v, dict) else v for k, v in kwargs.items()} return cached_func(*processed_args, **processed_kwargs) # 复制缓存操作方法到包装函数,方便用户清理/查看缓存 wrapped.cache_clear = cached_func.cache_clear wrapped.cache_info = cached_func.cache_info # 保留原函数的元信息(如函数名、文档字符串) return functools.wraps(func)(wrapped) return decorator
测试验证
使用修复后的代码,输入相同内容的字典时,lru_cache会基于BlackBox的内容哈希识别为同一参数,缓存将正常生效:
call_count = 0 @lru_cache_ignore() def test_func(a, config): global call_count call_count += 1 return a + sum(config.values()) def test_cache_hit(): global call_count call_count = 0 # 第一次调用 assert test_func(1, {"x": 2, "y": 3}) == 6 assert call_count == 1 # 第二次调用(字典键顺序不同,但内容一致) assert test_func(1, {"y": 3, "x": 2}) == 6 assert call_count == 1 # 缓存命中,调用次数未增加
内容的提问来源于stack exchange,提问作者Sanandrea
相关产品推荐
相关产品推荐

