You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 21:07:49