2015-02-04 3 views
3

Я ищу способ построения декоратора @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, что заставляет повторный показ, но does not update the cache.

Последующая деятельность по использованию klepto:

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

cache_path = "/some/path/" 

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

+1

'functools.lru_cache' является запоминанием декоратора, который обеспечивает способ, чтобы очистить весь кэш (но не определенный вызов, как в вашем пример). – interjay

ответ

3

Я предлагаю посмотреть 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 

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

+0

Спасибо Уолли. Я уже использую 'joblib' и не могу найти способ делать то, что я прошу (самое близкое -' .call', но, как я упоминаю в своей заметке, он не делает то, что мне нужно). Я посмотрю на «клепто». –

+1

Я вижу ваше редактирование. Я считаю, что 'klepto' делает то, что вы ищете, поскольку он предоставляет интерфейс' dict' для любого кеша, а также предоставляет метод 'pop' для кеша - он предоставляет все методы' dict', на самом деле. –

+0

Это очень полезно. Спасибо, Уолли. Это похоже на более полную библиотеку для memoization, а не joblib. Замечательно знать. На этом примечании, знаете ли вы, как это сделать с ** диском ** persistance? –

2

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

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.Так что вы did't вызвать функцию с этими параметрами или вы передаете 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] 
1

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

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 
1

Это чисто с точки зрения к вопросу о последующих мерах по klepto ...

растекания продлит @ например Уолли указать директорию:

>>> 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') 
+0

Отлично. Спасибо @Mike! Я прочитаю документы, но каковы предпосылки для конкретной ключевой карты, которую вы использовали? т.е. 'keymap = hashmap (algorithm = 'md5')' –

+0

Я не уверен, что вы подразумеваете под «предположениями» ... но, возможно, эта ссылка пояснит: https://github.com/uqfoundation/klepto/blob/ master/klepto/keymaps.py, а набор «кодировок» можно найти здесь: https://github.com/uqfoundation/klepto/blob/master/klepto/crypto.py Я использовал раскладку, которая использовалась в данный пример. Дайте мне знать, если вышесказанное не отвечает на ваши вопросы. –

Смежные вопросы