簡體   English   中英

將緩存存儲到文件 functools.lru_cache 中 Python >= 3.2

[英]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有用的東西

它旨在幫助您加快長時間運行方法的迭代。 它可以通過配置文件進行配置

@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 已經具有基於文件的緩存。 有關詳細信息,請參閱文檔

話雖這么說,我有一些額外的要求:

  1. 我希望能夠在測試中直接調用緩存的 function 而不是從夾具中調用
  2. 我想緩存復雜的 python 對象,而不僅僅是簡單的 python 基元/容器
  3. 我想要一個可以智能刷新緩存的實現(或被迫僅使單個鍵無效)

因此,我想出了自己的 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM