The latest version of Python introduced a new language feature, function and method decorators (PEP 318, http://www.python.org/peps/pep-0318.html). This recipe show a common callable transformation that can benefit from the new syntax, often referred to as Memoization pattern.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | import types
def cachedmethod(function):
return types.MethodType(Memoize(function), None)
class Memoize:
def __init__(self,function):
self._cache = {}
self._callable = function
def __call__(self, *args, **kwds):
cache = self._cache
key = self._getKey(*args,**kwds)
try: return cache[key]
except KeyError:
cachedValue = cache[key] = self._callable(*args,**kwds)
return cachedValue
def _getKey(self,*args,**kwds):
return kwds and (args, ImmutableDict(kwds)) or args
class ImmutableDict(dict):
'''A hashable dict.'''
def __init__(self,*args,**kwds):
dict.__init__(self,*args,**kwds)
def __setitem__(self,key,value):
raise NotImplementedError, "dict is immutable"
def __delitem__(self,key):
raise NotImplementedError, "dict is immutable"
def clear(self):
raise NotImplementedError, "dict is immutable"
def setdefault(self,k,default=None):
raise NotImplementedError, "dict is immutable"
def popitem(self):
raise NotImplementedError, "dict is immutable"
def update(self,other):
raise NotImplementedError, "dict is immutable"
def __hash__(self):
return hash(tuple(self.iteritems()))
if __name__ == '__main__':
from math import sqrt,log,sin,cos
class Example:
def __init__(self,x,y):
# self._x and self._y should not be changed after initialization
self._x = x
self._y = y
@cachedmethod
def computeSomething(self, alpha, beta):
w = log(alpha) * sqrt(self._x * alpha + self._y * beta)
z = log(beta) * sqrt(self._x * beta + self._y * alpha)
return sin(z) / cos(w)
|
Memoization is a useful pattern for improving the amortized performance of a computationally expensive function or method. The new syntax allows making explicit that a callable is memoized.
A restriction of the recipe above is that the arguments of the function (and also the method receiver object for methods) have to be hashable.
The decorator can be generalized by allowing different caching policies (e.g. a FIFO cache or a cache implementing an LRU policy) apart from the implied "cache-forever" policy of a dict.