def get_important_data(self): if not hasattr(self, _important_data): self._important_data = self.get_result_of_lots_of_queries() return self._important_dataIf you call this function once on an object, it will run a lot of queries, but will store the result in self._important_data. The next time you call the function on this exact same object, the attribute self._important_data will still be set and the huge load of queries do not have to be executed again.
In some projects you have to create such a function more than once, for whatever reason. A colleague got sick and tired of writing the same code over and over again and came up with an idea to tackle this concurrency. So he asked me if, when I had little to do, I could write a decorator that does exactly the same kind of 'caching' as the function above. I however have little to no experience in writing decorators, so I copy/pasted something together from the interwebz, that seems to do what I want.
This is what I came up with.
in decorators.py:
from functools import wraps def cached_function(attr): def inner_cached_function(fn): def return_attr(*args, **kwargs): try: cls = args[0].__class__ # args[0] should be fn's self except: raise Exception("""A function with the cached_function decorator must use 'self' as first argument""") if not hasattr(cls, attr): value = fn(*args, **kwargs) setattr(cls, attr, value) return getattr(cls, attr) return wraps(fn)(return_attr) return inner_cached_functionin a ModelClass:
@cached_function('_important_data') def get_important_data(self): return self.get_result_of_lots_of_queries()As said/written before, I almost have no experience with this, so please comment if you have anything to say about this solution.
Thanks :-)
You may have a look at plone.memoize, it's been here for years.
ReplyDeletehttp://pypi.python.org/pypi/plone.memoize
In particular plone.memoize.instance
I didn't check if it has dependencies on other plone or zope components, but you can always extract the presumably well-written, interesting parts.
Maybe interesting too (and more 'core' python): http://pypi.python.org/pypi/memojito
This in itself is a very good practice, it limits the number of responsibilities for your class and allows greater flexibility in adding or removing the cache.
ReplyDeleteYou should also consider an expiration of your cached data, as the cached data could change every once in a while. This could be an option for your decorator?
If the decorated method has parameters, you could use the parameters to build a key for a dictionary, and store the cached data there.