2011-02-07 67 views
3

我要的是一個記憶化裝飾器:可以做些什麼來加速這個memoization裝飾器?

我已經調整了我看到了一個例子,有以下想出了:

import functools 

class Memoized(object): 
    """Decorator that caches a function's return value each time it is called. 
    If called later with the same arguments, the cached value is returned, and 
    not re-evaluated. 
    """ 

    __cache = {} 

    def __init__(self, func): 
    self.func = func 
    self.key = (func.__module__, func.__name__) 

    def __call__(self, *args): 
    try: 
     return Memoized.__cache[self.key][args] 
    except KeyError: 
     value = self.func(*args) 
     Memoized.__cache[self.key] = {args : value} 
     return value 
    except TypeError: 
     # uncachable -- for instance, passing a list as an argument. 
     # Better to not cache than to blow up entirely. 
     return self.func(*args) 

    def __get__(self, obj, objtype): 
    """Support instance methods.""" 
    return functools.partial(self.__call__, obj) 

    @staticmethod 
    def reset(): 
    Memoized.__cache = {} 

我與它的問題是,緩存部分似乎涉及大量的開銷(例如:遞歸函數)。以下面的函數爲例,我可以在比memoized版本更少的時間內用非memoized版本調用fib(30)十次。

def fib(n): 

    if n in (0, 1): 
     return n 
    return fib(n-1) + fib(n-2) 

任何人都可以提出一個更好的方式來寫這個裝飾? (或者將我指向一個更好的(即更快的)裝飾器,它可以做我想做的)。 我對保留方法簽名不感興趣,或幫助內省工具「知道」關於裝飾函數的任何內容。

謝謝。

P.S.使用Python 2.7

回答

12

你實際上沒有任何緩存數據,因爲每次你設置一個新的緩存值,你覆蓋以前:

Memoized.__cache[self.key] = {args : value} 

如。

import functools 

class Memoized(object): 
    """Decorator that caches a function's return value each time it is called. 
    If called later with the same arguments, the cached value is returned, and 
    not re-evaluated. 
    """ 

    cache = {} 

    def __init__(self, func): 
     self.func = func 
     self.key = (func.__module__, func.__name__) 
     self.cache[self.key] = {} 

    def __call__(self, *args): 
     try: 
      return Memoized.cache[self.key][args] 
     except KeyError: 
      value = self.func(*args) 
      Memoized.cache[self.key][args] = value 
      return value 
     except TypeError: 
      # uncachable -- for instance, passing a list as an argument. 
      # Better to not cache than to blow up entirely. 
      return self.func(*args) 

    def __get__(self, obj, objtype): 
     """Support instance methods.""" 
     return functools.partial(self.__call__, obj) 

    @staticmethod 
    def reset(): 
     Memoized.cache = {} 
  • FIB(30)無緩存:2.86742401123
  • FIB(30)高速緩存:0.000198125839233

其他一些注意事項:

  • 不要使用__prefix;沒有理由在這裏,它只是醜化代碼。
  • 而不是使用一個單一的,整體的,類屬性字典,給的Memoized自己的字典每個實例,並保持Memoized物體登記冊。這改進了封裝,並消除了依賴於模塊和函數名稱的奇怪之處。

import functools 
import weakref 

class Memoized(object): 
    """Decorator that caches a function's return value each time it is called. 
    If called later with the same arguments, the cached value is returned, and 
    not re-evaluated. 

    >>> counter = 0 
    >>> @Memoized 
    ... def count(): 
    ...  global counter 
    ...  counter += 1 
    ...  return counter 

    >>> counter = 0 
    >>> Memoized.reset() 
    >>> count() 
    1 
    >>> count() 
    1 
    >>> Memoized.reset() 
    >>> count() 
    2 

    >>> class test(object): 
    ...  @Memoized 
    ...  def func(self): 
    ...   global counter 
    ...   counter += 1 
    ...   return counter 
    >>> testobject = test() 
    >>> counter = 0 
    >>> testobject.func() 
    1 
    >>> testobject.func() 
    1 
    >>> Memoized.reset() 
    >>> testobject.func() 
    2 
    """ 

    caches = weakref.WeakSet() 

    def __init__(self, func): 
     self.func = func 
     self.cache = {} 
     Memoized.caches.add(self) 

    def __call__(self, *args): 
     try: 
      return self.cache[args] 
     except KeyError: 
      value = self.func(*args) 
      self.cache[args] = value 
      return value 
     except TypeError: 
      # uncachable -- for instance, passing a list as an argument. 
      # Better to not cache than to blow up entirely. 
      return self.func(*args) 

    def __get__(self, obj, objtype): 
     """Support instance methods.""" 
     return functools.partial(self.__call__, obj) 

    @staticmethod 
    def reset(): 
     for memo in Memoized.caches: 
      memo.cache = {} 

if __name__ == '__main__': 
    import doctest 
    doctest.testmod() 

編輯:添加測試,並使用weakref.WeakSet。請注意,WeakSet僅在2中可用。7(OP正在使用);對於在2.6版本中運行的版本,請參閱編輯歷史記錄。

+0

哦。麻煩,你說得對。我應該注意到這一點。 :-)我的版本仍然會更快,但幾乎沒有這麼多。 – Omnifarious 2011-02-07 22:43:51

1

這裏是一個版本,是顯著更快。不幸的是,reset不再能夠實際上完全清除緩存,因爲所有實例都存儲了它們自己的對每個函數字典的引用的本地副本。雖然你可以排序的得到它的工作:

import functools 

class Memoized(object): 
    """Decorator that caches a function's return value each time it is called. 
    If called later with the same arguments, the cached value is returned, and 
    not re-evaluated. 
    """ 

    __cache = {} 

    def __init__(self, func): 
    self.func = func 
    key = (func.__module__, func.__name__) 
    if key not in self.__cache: 
     self.__cache[key] = {} 
    self.mycache = self.__cache[key] 

    def __call__(self, *args): 
    try: 
     return self.mycache[args] 
    except KeyError: 
     value = self.func(*args) 
     self.mycache[args] = value 
     return value 
    except TypeError: 
     # uncachable -- for instance, passing a list as an argument. 
     # Better to not cache than to blow up entirely. 
     return self.func(*args) 

    def __get__(self, obj, objtype): 
    """Support instance methods.""" 
    return functools.partial(self.__call__, obj) 

    @staticmethod 
    def reset(): 
    for v in self.__cache.itervalues(): 
     v.clear() 
相關問題