PhE*_*PhE 1 python caching functools
我不明白如何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
Run Code Online (Sandbox Code Playgroud)
如何缓存具有相同哈希的对象实例的调用?
简短的回答:为了在已经在缓存中o2时获得缓存命中o1,该类可以定义一个__eq__()方法来比较Query对象是否具有相等的值。
例如:
def __eq__(self, other):
return isinstance(other, Query) and self.id == other.id
Run Code Online (Sandbox Code Playgroud)
更新:还有一个细节值得在摘要中提及,而不是隐藏在细节中:此处描述的行为也适用于functools.cachePython 3.9 中引入的包装器,因为@cache()它只是@lru_cache(maxsize=None).
长答案(包括o3):
这里有关于字典查找的确切机制的很好的解释,所以我不会重新创建它。可以这么说,由于 LRU 缓存存储为字典,因此由于字典键的比较方式,类对象需要比较为相等才能被视为已存在于缓存中。
您可以在一个使用常规字典的快速示例中看到这一点,其中有两个版本的类,一个使用__eq__()而另一个不使用:
>>> o1 = Query_with_eq(33)
>>> o2 = Query_with_eq(33)
>>> {o1: 1, o2: 2}
{<__main__.Query_with_eq object at 0x6fffffea9430>: 2}
Run Code Online (Sandbox Code Playgroud)
这会导致字典中出现一项,因为键是相等的,而:
>>> 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}
Run Code Online (Sandbox Code Playgroud)
结果是两个项目(不相等的键)。
当对象存在时,为什么int不会导致缓存命中Query:
o3是一个规则的int物体。虽然它的值确实比较等于Query(33),但假设Query.__eq__()正确比较类型,lru_cache则具有绕过该比较的优化。
通常,为包装函数的参数lru_cache创建一个字典键(作为 a )。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
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
4353 次 |
| 最近记录: |