Декораторы для выборочного кэширования / запоминания
Я ищу способ создания декоратора @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')