1import types 2import functools 3 4 5# from jaraco.functools 3.3 6def method_cache(method, cache_wrapper=None): 7 """ 8 Wrap lru_cache to support storing the cache data in the object instances. 9 10 Abstracts the common paradigm where the method explicitly saves an 11 underscore-prefixed protected property on first call and returns that 12 subsequently. 13 14 >>> class MyClass: 15 ... calls = 0 16 ... 17 ... @method_cache 18 ... def method(self, value): 19 ... self.calls += 1 20 ... return value 21 22 >>> a = MyClass() 23 >>> a.method(3) 24 3 25 >>> for x in range(75): 26 ... res = a.method(x) 27 >>> a.calls 28 75 29 30 Note that the apparent behavior will be exactly like that of lru_cache 31 except that the cache is stored on each instance, so values in one 32 instance will not flush values from another, and when an instance is 33 deleted, so are the cached values for that instance. 34 35 >>> b = MyClass() 36 >>> for x in range(35): 37 ... res = b.method(x) 38 >>> b.calls 39 35 40 >>> a.method(0) 41 0 42 >>> a.calls 43 75 44 45 Note that if method had been decorated with ``functools.lru_cache()``, 46 a.calls would have been 76 (due to the cached value of 0 having been 47 flushed by the 'b' instance). 48 49 Clear the cache with ``.cache_clear()`` 50 51 >>> a.method.cache_clear() 52 53 Same for a method that hasn't yet been called. 54 55 >>> c = MyClass() 56 >>> c.method.cache_clear() 57 58 Another cache wrapper may be supplied: 59 60 >>> cache = functools.lru_cache(maxsize=2) 61 >>> MyClass.method2 = method_cache(lambda self: 3, cache_wrapper=cache) 62 >>> a = MyClass() 63 >>> a.method2() 64 3 65 66 Caution - do not subsequently wrap the method with another decorator, such 67 as ``@property``, which changes the semantics of the function. 68 69 See also 70 http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/ 71 for another implementation and additional justification. 72 """ 73 cache_wrapper = cache_wrapper or functools.lru_cache() 74 75 def wrapper(self, *args, **kwargs): 76 # it's the first call, replace the method with a cached, bound method 77 bound_method = types.MethodType(method, self) 78 cached_method = cache_wrapper(bound_method) 79 setattr(self, method.__name__, cached_method) 80 return cached_method(*args, **kwargs) 81 82 # Support cache clear even before cache has been created. 83 wrapper.cache_clear = lambda: None 84 85 return wrapper 86