I'm attempting to build a decorator for an instance method of a class that will memoize the result. (This has been done a million times before) However, I'd like the option of being able to reset the memoized cache at any point (say, if something in the instance state changes, which might change the result of the method having nothing to do with its args). So, I attempted to build a decorator as a class instead of a function, so that I might have access to the cache as a class member. This led me down the path of learning about descriptors, specifically the __get__
method, which is where I'm actually stuck. My code looks like so:
import time
class memoized(object):
def __init__(self, func):
self.func = func
self.cache = {}
def __call__(self, *args, **kwargs):
key = (self.func, args, frozenset(kwargs.iteritems()))
try:
return self.cache[key]
except KeyError:
self.cache[key] = self.func(*args, **kwargs)
return self.cache[key]
except TypeError:
# uncacheable, so just return calculated value without caching
return self.func(*args, **kwargs)
# self == instance of memoized
# obj == instance of my_class
# objtype == class object of __main__.my_class
def __get__(self, obj, objtype=None):
"""Support instance methods"""
if obj is None:
return self
# new_func is the bound method my_func of my_class instance
new_func = self.func.__get__(obj, objtype)
# instantiates a brand new class...this is not helping us, because it's a
# new class each time, which starts with a fresh cache
return self.__class__(new_func)
# new method that will allow me to reset the memoized cache
def reset(self):
print "IN RESET"
self.cache = {}
class my_class:
@memoized
def my_func(self, val):
print "in my_func"
time.sleep(2)
return val
c = my_class()
print "should take time"
print c.my_func(55)
print
print "should be instant"
print c.my_func(55)
print
c.my_func.reset()
print "should take time"
print c.my_func(55)
Is this clear and/or possible? Each time __get__
is called, I get a brand new instance of the memoized class, which loses me the cache with actual data in it. I've been working hard with __get__
, but am not making much progress.
Is there a completely separate approach to this problem that I'm completely missing? And and all advice/suggestions are welcome and appreciated. Thanks.
Well, I would like to point out two performance issues in your code. This is not an answer to your question, but I can't make it a comment. Thanks to @delnan for pointing out that
has_key
is deprecated. Instead of:I would make it this way:
This avoids: a) try/except
KeyError
; b) callingcache[key]
on return; c) calling the function once more on unhashable keys.Building upon the answer to the original question given by @aix I have created a class that I think could improve it. The main feature is that the cached values are stored as a property of the instance whose method is being decorated, hence it is very easy to reset them.
As an example of usage:
gets as output:
Rather than trying to work out the mechanics of your implementation, I've taken the
memoized
decorator class from PythonDecoratorLibrary, and have modified it to addreset
. Below is the result; the trick I've used is to add a callablereset
attribute to the decorated function itself.