Timed Caching Decorator

Here's the caching decorator I mentioned in my previous blog entry about optimizing the front page of becontrary.com. It is pretty simple to use, you supply the same parameters to it as timedelata . So @timed_cache(minutes=30) would cache the result for half an hour. It is thread safe, so you can happily use it in the context of a web application, such as Turbogears. For many things it is a magical one-line speed-up, but there are some issues to be aware of. You can't use it to cache anything where the return value is context sensitive, such as SQLObject class instances -- because they are tied to a database context that wont exist at the second call. If you do want to cache such objects, then you should copy the information you need to a dictionary. Genshi templates make the difference completely transparent, so it is not much of a problem. Another issue is that parameters must be immutable, because they are used as keys in a dictionary.

from datetime import datetime , timedelta from copy import deepcopy from threading import RLock def timed_cache ( seconds = 0 , minutes = 0 , hours = 0 , days = 0 ): time_delta = timedelta ( seconds = seconds , minutes = minutes , hours = hours , days = days ) def decorate ( f ): f . _lock = RLock () f . _updates = {} f . _results = {} def do_cache ( * args , ** kwargs ): lock = f . _lock lock . acquire () try : key = ( args , tuple ( sorted ( kwargs . items (), key = lambda i : i [ 0 ]))) updates = f . _updates results = f . _results t = datetime . now () updated = updates . get ( key , t ) if key not in results or t - updated > time_delta : # Calculate updates [ key ] = t result = f ( * args , ** kwargs ) results [ key ] = deepcopy ( result ) return result else : # Cache return deepcopy ( results [ key ]) finally : lock . release () return do_cache return decorate if __name__ == "__main__" : import time class T ( object ): @timed_cache ( seconds = 2 ) def expensive_func ( self , c ): time . sleep ( . 2 ) return c t = T () for _ in xrange ( 30 ): time . sleep ( . 1 ) t1 = time . clock () print t . expensive_func ( 'Calling expensive method' ) print "t - %i milliseconds" % int ( ( time . clock () - t1 ) * 1000. )

There are some other things to be aware of. Naturally it will use up more memory, because a copy of the result is stored for each combination of parameters. And the standard disclaimer applies, that you should check if there is actually a speed-up before using it in production code.