Caching result of a method in Python
Recently I was working on adding a new feature to an existing legacy code. But then while working on it, I found a sweet problem which was quite new for me.
def get_obj():
model_obj = model() # Some large database query
return model_obj def get_obj_value(self):
obj = self.get_obj()
return obj def get_obj_value_dup(self):
obj = self.get_obj()
return obj
As we can see I have a method get_obj() which does some database query and returns that. This method was already there and I just had to use it in my separate methods, but soon I realized that this method get_obj() was being used in multiple places and it was always making database query which is computationally very costly. Thus the idea of caching the return value of the method came to my mind.
Currently, in the above code snippet, we can see that the return value of the method is computed always. To reduce that I used a custom memoize decorator which would cache the result of a method with a single self
argument as property and its result will persist as long as the instance does.
def memoize(function):
memo = {} def wrapper(*args):
if args in memo:
return memo[args]
else:
rv = function(*args)
memo[args] = rv
return rv return wrapper@memoize
def get_obj():
model_obj = model()
return model_objdef get_obj_value(self):
obj = self.get_obj()
return objdef get_obj_value_dup(self):
obj = self.get_obj()
return obj
Here we can see that memorize stores its cached result in the memo dictionary. Now if we go inside the wrapper function, if args in memo-> here is checks that if the cached results already exist then return that without going inside the main function, else-> go inside the function, compute and then store the result and return the existing result.
Alternatively, another memoization decorator with a limit on the cache size can be implemented when you do not want your memory to be full if a single instance is caching quite big enough.
Also, we can use the python Funtool library’s cached_property
.
from functools import cached_property@cached_property
def get_obj():
model_obj = model()
return model_obj
Thus, we could see how the caching result of a method helped me in this case. But this power should be used wisely and not followed blindly everywhere.
There are only two hard things in Computer Science: cache invalidation and naming things. — Phil Karlton
Sometimes we need to work a little with a model instance and where we need to change the object and send it to the response. Django provides the refresh_from_db() method to reload an object from the database. Refreshing the object from the database all model fields and relationships are cleaned out and non-deferred fields are reloaded, but the cached method is not a model field and the value is still stored in self.__dict__.