程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长

+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

暂无数据

使用 lru_cache 和 __hash__ 缓存对象实例

发布于2022-10-06 22:15     阅读(753)     评论(0)     点赞(5)     收藏(3)


我不明白functools.lru_cache对象实例是如何工作的。我假设该类必须提供一种__hash__方法。所以任何具有相同哈希hit的实例都应该缓存。

这是我的测试:

from functools import lru_cache

class Query:    
    def __init__(self, id: str):
        self.id = id

    def __hash__(self):
        return hash(self.id)

@lru_cache()
def fetch_item(item):
    return 'data'

o1 = Query(33)
o2 = Query(33)
o3 = 33

assert hash(o1) == hash(o2) == hash(o3)

fetch_item(o1)  # <-- expecting miss
fetch_item(o1)  # <-- expecting hit
fetch_item(o2)  # <-- expecting hit BUT get a miss !
fetch_item(o3)  # <-- expecting hit BUT get a miss !
fetch_item(o3)  # <-- expecting hit

info = fetch_item.cache_info()
print(info)

assert info.hits == 4
assert info.misses == 1
assert info.currsize == 1

如何缓存具有相同哈希的对象实例的调用?


解决方案


简短的回答:为了在缓存中已经存在o2时获得缓存命中o1,该类可以定义一个__eq__()方法来比较Query对象是否具有相等的值。

例如:

def __eq__(self, other):
    return isinstance(other, Query) and self.id == other.id

更新:一个额外的细节值得在摘要中提及而不是埋在细节中:这里描述的行为也适用functools.cache于 Python 3.9 中引入的包装器,因为@cache()它只是@lru_cache(maxsize=None).

长答案(包括o3

这里有一个关于字典查找的确切机制的很好的解释,所以我不会重新创建它。可以这么说,由于 LRU 缓存被存储为 dict,类对象需要比较相等才能被认为已经存在于缓存中,因为字典键的比较方式。

您可以在一个带有常规字典的快速示例中看到这一点,该字典具有两个版本的类,一个使用__eq__()另一个不使用:

>>> o1 = Query_with_eq(33)
>>> o2 = Query_with_eq(33)
>>> {o1: 1, o2: 2}
{<__main__.Query_with_eq object at 0x6fffffea9430>: 2}

这会导致字典中有一项,因为键是相等的,而:

>>> o1 = Query_without_eq(33)
>>> o2 = Query_without_eq(33)
>>> {o1: 1, o2: 2}
{<__main__.Query_without_eq object at 0x6fffffea9cd0>: 1, <__main__.Query_without_eq object at 0x6fffffea9c70>: 2}

导致两个项目(不相等的键)。

为什么当对象存在int时不会导致缓存命中Query

o3是一个常规int对象。虽然它的值确实比较等于Query(33),但假设Query.__eq__()正确比较类型,lru_cache则具有绕过该比较的优化。

通常,lru_cache创建包装函数的参数的字典键(作为tuple)。可选地,如果缓存是使用typed=True参数创建的,它还存储每个参数的类型,因此只有当它们也是相同类型时值才相等。

优化之处在于,如果包装函数只有一个参数,并且类型为intor str,则直接将单个参数用作字典键,而不是变成元组。因此,(Query(33),)不要33比较相等,即使它们有效地存储了相同的值。(请注意,我并不是说int对象没有被缓存,只是它们与非类型的现有值不匹配int。从您的示例中,您可以看到fetch_item(o3)第二次调用时缓存命中)。

如果参数的类型不同于int. 例如,33.0将匹配,再次假定Query.__eq__()将类型考虑在内并返回True为此,您可以执行以下操作:

def __eq__(self, other):
    if isinstance(other, Query):
        return self.id == other.id
    else:
        return self.id == other


所属网站分类: 技术文章 > 问答

作者:黑洞官方问答小能手

链接:https://www.pythonheidong.com/blog/article/1793557/8f62d869a75f8bc6ba77/

来源:python黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

5 0
收藏该文
已收藏

评论内容:(最多支持255个字符)