functools.lru_cache与对象实例缓存疑问:相同哈希值实例为何无法命中缓存?
如何让
functools.lru_cache对哈希相同的对象实例命中缓存? 你猜中了一半——lru_cache确实依赖哈希值,但光有__hash__方法还不够,它判断两个参数是否为同一个缓存键的逻辑是:哈希值相同,并且两个对象的__eq__方法返回True。这就是你的测试代码没按预期命中的原因。
问题出在哪?
你的Query类只实现了__hash__,但Python默认的__eq__方法是比较对象的内存地址(也就是is判断):
o1和o2是两个不同的实例,所以o1 == o2返回False,哪怕哈希值相同,也不会被视为同一个缓存键;o3是整数33,和Query实例做相等比较时,默认的__eq__也会返回False,自然也无法命中缓存。
解决方案:实现__eq__方法
要让哈希相同的对象被缓存视为同一个键,必须给Query类补充__eq__方法,定义“相等”的规则。下面分两种场景给出实现:
场景1:仅让同类型的Query实例按id匹配
如果只需要相同id的Query实例互相命中缓存,可以这样写:
from functools import lru_cache class Query: def __init__(self, id: str): self.id = id def __hash__(self): return hash(self.id) def __eq__(self, other): # 仅和同类型对象比较id if not isinstance(other, Query): return False return self.id == other.id @lru_cache() def fetch_item(item): return 'data' o1 = Query("33") # 注意这里和__init__的参数类型保持一致(str) o2 = Query("33") fetch_item(o1) # miss fetch_item(o1) # hit fetch_item(o2) # hit(现在o1 == o2为True) info = fetch_item.cache_info() print(info) # CacheInfo(hits=2, misses=1, maxsize=None, currsize=1) assert info.hits == 2 assert info.misses == 1 assert info.currsize == 1
场景2:让Query实例和对应的整数/字符串也能匹配
如果需要Query("33")和整数33、字符串"33"都能命中同一个缓存,可以扩展__eq__的逻辑:
from functools import lru_cache class Query: def __init__(self, id: str): self.id = id def __hash__(self): return hash(self.id) def __eq__(self, other): # 处理和字符串、整数的跨类型比较 if isinstance(other, (str, int)): return str(self.id) == str(other) elif isinstance(other, Query): return self.id == other.id return False @lru_cache() def fetch_item(item): return 'data' o1 = Query("33") o2 = Query("33") o3 = 33 o4 = "33" fetch_item(o1) # miss fetch_item(o1) # hit fetch_item(o2) # hit fetch_item(o3) # hit(现在o1 == o3为True) fetch_item(o4) # hit(o1 == o4也为True) info = fetch_item.cache_info() print(info) # CacheInfo(hits=4, misses=1, maxsize=None, currsize=1) assert info.hits == 4 assert info.misses == 1 assert info.currsize == 1
注意事项
- 实现
__eq__时要遵循对称性原则:如果a == b为True,b == a也必须为True。比如上面的跨类型比较中,Query("33") == 33为True,但33 == Query("33")会返回False(因为整数的__eq__不会处理Query对象)。如果需要完全对称,建议在fetch_item函数内部先统一转换类型,比如把参数都转成字符串再处理。 __hash__和__eq__必须保持一致:如果a == b为True,那么hash(a)必须等于hash(b),这一点你已经通过基于id的哈希实现了。
内容的提问来源于stack exchange,提问作者PhE
相关产品推荐
相关产品推荐

