Декораторы для выборочного кэширования / запоминания

Я ищу способ создания декоратора @memoize что я могу использовать в функциях следующим образом:

@memoize
my_function(a, b, c):
    # Do stuff 
    # result may not always be the same for fixed (a,b,c)
return result

Тогда, если я сделаю:

result1 = my_function(a=1,b=2,c=3)
# The function f runs (slow). We cache the result for later

result2 = my_function(a=1, b=2, c=3)
# The decorator reads the cache and returns the result (fast)

Теперь скажите, что я хочу принудительно обновить кеш:

result3 = my_function(a=1, b=2, c=3, force_update=True)
# The function runs *again* for values a, b, and c. 

result4 = my_function(a=1, b=2, c=3)
# We read the cache

В конце вышесказанного у нас всегда есть result4 = result3, но не обязательно result4 = result, поэтому нужна опция принудительного обновления кэша для тех же входных параметров.

Как я могу подойти к этой проблеме?

Обратите внимание на joblib

Насколько я знаю joblibопоры.call, что вызывает повторный запуск, но не обновляет кэш.

Продолжение использования klepto:

Есть ли способ иметь klepto (см. ответ @Wally) кэшировать его результаты по умолчанию в определенном месте? (например /some/path/) и разделить это местоположение между несколькими функциями? Например, я хотел бы сказать,

cache_path = "/some/path/"

а потом @memoize несколько функций в данном модуле по одному пути.

4 ответа

Решение

Я бы предложил посмотреть на joblib а также klepto, Оба имеют очень настраиваемые алгоритмы кэширования и могут делать то, что вы хотите.

Оба определенно могут сделать кеширование для result1 а также result2, а также klepto обеспечивает доступ к кешу, поэтому можно pop результат из локального кэша памяти (без удаления его из сохраненного архива, скажем, в базе данных).

>>> import klepto
>>> from klepto import lru_cache as memoize
>>> from klepto.keymaps import hashmap
>>> hasher = hashmap(algorithm='md5')
>>> @memoize(keymap=hasher)
... def squared(x):
...   print("called")
...   return x**2
... 
>>> squared(1)
called
1
>>> squared(2)
called
4
>>> squared(3)
called
9
>>> squared(2)
4
>>> 
>>> cache = squared.__cache__()
>>> # delete the 'key' for x=2
>>> cache.pop(squared.key(2))
4
>>> squared(2)
called
4

Не совсем интерфейс ключевого слова, который вы искали, но он имеет функциональность, которую вы ищете.

Вы можете сделать что-то вроде этого:

import cPickle


def memoize(func):
    cache = {}

    def decorator(*args, **kwargs):
        force_update = kwargs.pop('force_update', None)
        key = cPickle.dumps((args, kwargs))
        if force_update or key not in cache:
            res = func(*args, **kwargs)
            cache[key] = res
        else:
            res = cache[key]
        return res
    return decorator

Декоратор принимает дополнительный параметр force_update (вам не нужно объявлять это в вашей функции). Это выскакивает из kwargs, Так что вы не вызывали функцию с этими параметрами ИЛИ вы передаете force_update = True функция будет вызвана:

@memoize
def f(a=0, b=0, c=0):
    import random
    return [a, b, c, random.randint(1, 10)]


>>> print f(a=1, b=2, c=3)
[1, 2, 3, 9]
>>> print f(a=1, b=2, c=3) # value will be taken from the cache
[1, 2, 3, 9]
>>> print f(a=1, b=2, c=3, force_update=True)
[1, 2, 3, 2]
>>> print f(a=1, b=2, c=3) # value will be taken from the cache as well
[1, 2, 3, 2]

Если вы хотите сделать это самостоятельно:

def memoize(func):
    cache = {}
    def cacher(a, b, c, force_update=False):
        if force_update or (a, b, c) not in cache:
            cache[(a, b, c)] = func(a, b, c)
        return cache[(a, b, c)]
    return cacher

Это чисто в отношении последующего вопроса для klepto...

Поток будет расширять пример @Wally, чтобы указать каталог:

>>> import klepto
>>> from klepto import lru_cache as memoize
>>> from klepto.keymaps import hashmap
>>> from klepto.archives import dir_archive
>>> hasher = hashmap(algorithm='md5')
>>> dir_cache = dir_archive('/tmp/some/path/squared')
>>> dir_cache2 = dir_archive('/tmp/some/path/tripled')
>>> @memoize(keymap=hasher, cache=dir_cache)
... def squared(x):
...   print("called")
...   return x**2
>>> 
>>> @memoize(keymap=hasher, cache=dir_cache2)
... def tripled(x):
...   print('called')
...   return 3*x
>>>

Вы можете поочередно использовать file_archiveгде вы указываете путь как:

cache = file_archive('/tmp/some/path/file.py') 
Другие вопросы по тегам