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:

  1. __init__ first makes sure the method has signature (self) and create the func_key for the cache
  2. __get__ creates the instance cache if None then returns the bound function
  3. __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 :)