[英]Store the cache to a file functools.lru_cache in Python >= 3.2
我在 Python 3.3 中使用@functools.lru_cache
。 我想將緩存保存到一個文件中,以便在程序重新啟動時恢復它。 我怎么辦?
編輯 1可能的解決方案: 我們需要挑選任何類型的可調用對象
問題酸洗__closure__
:
_pickle.PicklingError: Can't pickle <class 'cell'>: attribute lookup builtins.cell failed
如果我嘗試在沒有它的情況下恢復 function,我會得到:
TypeError: arg 5 (closure) must be tuple
您不能使用lru_cache
做您想做的lru_cache
,因為它不提供訪問緩存的 API,並且可能會在未來版本lru_cache
C 重寫。 如果您真的想保存緩存,則必須使用不同的解決方案來訪問緩存。
自己寫一個緩存很簡單。 例如:
from functools import wraps
def cached(func):
func.cache = {}
@wraps(func)
def wrapper(*args):
try:
return func.cache[args]
except KeyError:
func.cache[args] = result = func(*args)
return result
return wrapper
然后,您可以將其用作裝飾器:
>>> @cached
... def fibonacci(n):
... if n < 2:
... return n
... return fibonacci(n-1) + fibonacci(n-2)
...
>>> fibonacci(100)
354224848179261915075L
並檢索cache
:
>>> fibonacci.cache
{(32,): 2178309, (23,): 28657, ... }
然后,您可以隨意腌制/取消腌制緩存並加載它:
fibonacci.cache = pickle.load(cache_file_object)
我在 python 的問題跟蹤器中發現了一個功能請求,將轉儲/加載添加到lru_cache
,但它沒有被接受/實現。 也許將來可以通過lru_cache
為這些操作提供內置支持。
你可以使用我的圖書館, mezmorize
import random
from mezmorize import Cache
cache = Cache(CACHE_TYPE='filesystem', CACHE_DIR='cache')
@cache.memoize()
def add(a, b):
return a + b + random.randrange(0, 1000)
>>> add(2, 5)
727
>>> add(2, 5)
727
考慮使用joblib.Memory對磁盤進行持久緩存。
由於磁盤很大,因此不需要 LRU 緩存方案。
除了公共 API 之外,您不應該觸及裝飾器實現中的任何內容,因此如果您想更改其行為,您可能需要復制其實現並自己添加必要的功能。 請注意,緩存當前存儲為循環雙向鏈表,因此在保存和加載它時需要小心。
它旨在幫助您加快長時間運行方法的迭代。 它可以通過配置文件進行配置
@devcache(group='crm')
def my_method(a, b, c):
...
@devcache(group='db')
def another_method(a, b, c):
...
緩存可以刷新或與 yaml 配置文件一起使用,例如:
refresh: false # refresh true will ignore use_cache and refresh all cached data
props:
1:
group: crm
use_cache: false
2:
group: db
use_cache: true
將刷新my_method
的緩存並使用another_method
的緩存。
它不會幫助您腌制可調用的,但它會執行緩存部分,並且可以直接修改代碼以添加專門的序列化。
如果您的用例是在 pytest 測試套件中緩存計算密集型函數的結果,則 pytest 已經具有基於文件的緩存。 有關詳細信息,請參閱文檔。
話雖這么說,我有一些額外的要求:
因此,我想出了自己的 pytest 緩存包裝器,您可以在下面找到它。 該實施已完整記錄,但如果您需要更多信息,請告訴我,我很樂意編輯此答案:)
享受:
from base64 import b64encode, b64decode
import hashlib
import inspect
import pickle
from typing import Any, Optional
import pytest
__all__ = ['cached']
@pytest.fixture
def cached(request):
def _cached(func: callable, *args, _invalidate_cache: bool = False, _refresh_key: Optional[Any] = None, **kwargs):
"""Caches the result of func(*args, **kwargs) cross-testrun.
Cache invalidation can be performed by passing _invalidate_cache=True or a _refresh_key can
be passed for improved control on invalidation policy.
For example, given a function that executes a side effect such as querying a database:
result = query(sql)
can be cached as follows:
refresh_key = query(sql=fast_refresh_sql)
result = cached(query, sql=slow_or_expensive_sql, _refresh_key=refresh_key)
or can be directly invalidated if you are doing rapid iteration of your test:
result = cached(query, sql=sql, _invalidate_cache=True)
Args:
func (callable): Callable that will be called
_invalidate_cache (bool, optional): Whether or not to invalidate_cache. Defaults to False.
_refresh_key (Optional[Any], optional): Refresh key to provide a programmatic way to invalidate cache. Defaults to None.
*args: Positional args to pass to func
**kwargs: Keyword args to pass to func
Returns:
_type_: _description_
"""
# get debug info
# see https://stackoverflow.com/a/24439444/4442749
try:
func_name = getattr(func, '__name__', repr(func))
except:
func_name = '<function>'
try:
caller = inspect.getframeinfo(inspect.stack()[1][0])
except:
func_name = '<file>:<lineno>'
call_key = _create_call_key(func, None, *args, **kwargs)
cached_value = request.config.cache.get(call_key, {"refresh_key": None, "value": None})
value = cached_value["value"]
current_refresh_key = str(b64encode(pickle.dumps(_refresh_key)), encoding='utf8')
cached_refresh_key = cached_value.get("refresh_key")
if (
_invalidate_cache # force invalidate
or cached_refresh_key is None # first time caching this call
or current_refresh_key != cached_refresh_key # refresh_key has changed
):
print("Cache invalidated for '%s' @ %s:%d" % (func_name, caller.filename, caller.lineno))
result = func(*args, **kwargs)
value = str(b64encode(pickle.dumps(result)), encoding='utf8')
request.config.cache.set(
key=call_key,
value={
"refresh_key": current_refresh_key,
"value": value
}
)
else:
print("Cache hit for '%s' @ %s:%d" % (func_name, caller.filename, caller.lineno))
result = pickle.loads(b64decode(bytes(value, encoding='utf8')))
return result
return _cached
_args_marker = object()
_kwargs_marker = object()
def _create_call_key(func: callable, refresh_key: Any, *args, **kwargs):
"""Produces a hex hash str of the call func(*args, **kwargs)"""
# producing a key from func + args
# see https://stackoverflow.com/a/10220908/4442749
call_key = pickle.dumps(
(func, refresh_key) +
(_args_marker, ) +
tuple(args) +
(_kwargs_marker,) +
tuple(sorted(kwargs.items()))
)
# create a hex digest of the key for the filename
m = hashlib.sha256()
m.update(bytes(call_key))
return m.digest().hex()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.