Python Decorator to Memoize Instance Methods

Graham Jenson
Maori Geek
Published in
2 min readJun 30, 2021

--

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 @cache and @cached_property 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 :)

--

--