For the life of me I could not find a python decorator that memoizes methods on an instance. Something that replaces the pattern:
class A: def x(self): if self._x is not None: return self._x self._x = something_difficult() return self._x
Sure there is [functools](https://docs.python.org/3.9/library/functools.html) [] (https://docs.python.org/3.9/library/functools.html)`[@cache](https://docs.python.org/3.9/library/functools.html)` [and] (https://docs.python.org/3.9/library/functools.html)`[@cached_property](https://docs.python.org/3.9/library/functools.html)` but these use a global lru_cache for everything. I want the cache to live and die with the instance.
I am still pretty new to python, but slowly getting the hang of it. That being said I came up with this:
`class memoize:
def init(self, func):
self.func = func
fargspec = inspect.getfullargspec(func)
if len(fargspec.args) != 1 or fargspec.args[0] != “self”:
raise “@memoize must be (self)”
set key for this function
self.func_key = str(func)``def get(self, instance, cls):
if instance is None:
raise “@memoize’s must be bound”
Set the cache
if not hasattr(instance, “_memoize_cache”):
setattr(instance, “_memoize_cache”, {})
return function bound to instance
return types.MethodType(self, instance)
def call(self, *args, **kwargs):
args[0] will always be self
instance = args[0]
cache = instance._memoize_cache
return if cached
if self.func_key in cache:
return cache[self.func_key]
calculate and cache
result = self.func(*args, **kwargs)
cache[self.func_key] = result
return result`
The quick breakdown of the memoize decorator is:
__init__first makes sure the method has signature(self)and create thefunc_keyfor the cache__get__creates the instance cache ifNonethen returns the bound function__call__gets the instance cache, returns value if function already cached, otherwise calls the wrapped function and caches the result
And now the function looks much nicer:
class A: @memoize def x(self): return something_difficult()
This might be a bad idea for some reason (given that I couldn’t find anyone implementing this). Friendly comments are welcome :)