I have a working memoize decorator which uses Django’s cache backend to remember the result of a function for a certain amount of time. I am specifically applying this to a class method.
My decorator looks like:
def memoize(prefix='mysite', timeout=300, keygenfunc=None):
# MUST SPECIFY A KEYGENFUNC(args, kwargs) WHICH MUST RETURN A STRING
def funcwrap(meth):
def keymaker(*args, **kwargs):
key = prefix + '___' + meth.func_name + '___' + keygenfunc(args, kwargs)
return key
def invalidate(*args, **kwargs):
key = keymaker(*args, **kwargs)
cache.set(key, None, 1)
def newfunc(*args, **kwargs):
# construct key
key = keymaker(*args, **kwargs)
# is in cache?
rv = cache.get(key)
if rv is None:
# cache miss
rv = meth(*args, **kwargs)
cache.set(key, rv, timeout)
return rv
newfunc.invalidate = invalidate
return newfunc
return funcwrap
I am using this on a class method, so something like:
class StorageUnit(models.Model):
@memoize(timeout=60*180, keygenfunc=lambda x,y: str(x[0].id))
def someBigCalculation(self):
...
return result
The actual memoization process works perfectly! That is, a call to
myStorageUnitInstance.someBigCalculation()
properly uses the cache. OK, cool!
My problem is when I try to manually invalidate the entry for a specific instance, where I want to be able to run
myStorageUnitInstance.someBigCalculation.invalidate()
However, this doesn’t work, because “self” doesn’t get passed in and therefore the key doesn’t get made. I get a “IndexError: tuple index out of range” error pointing to my lambda function as shown earlier.
Of course, I can successfully call:
myStorageUnitInstance.someBigCalculation.invalidate(myStorageUnitInstance)
and this works perfectly. But it “feels” redundant when I’m already referencing a specific instance. How can I make Python treat this as an instance-bound method and therefore properly fill in the “self” variable?
Descriptors must always be set on the class, not on the instance (see the how-to guide for all details). Of course, in this case you’re not even setting it on the instance, but rather on another function (and fetching it as an attribute of a bound method). I think that the only way to use the syntax you want is to make funcwrap an instance of a custom class (which class must be a descriptor class, of course, i.e., define the appropriate
__get__method, just like functions intrinsically do). Theninvalidatecan be a method of that class (or, perhaps better, the other custom class whose instance is the “bound-method-like substance” produced by the formerly mentioned descriptor class’s__get__method), and eventually reach theim_self(that’s how it’s named in a bound method) that you crave.A pretty hefty (conceptual and coding;-) price to pay for the minor convenience you seek — hefty enough that I don’t really feel like spending an hour or two developing it completely and testing it. But I hope I’ve given you clear-enough indications for you to proceed if you’re still keen on this, and indeed I’ll be glad to clarify and help out if there’s anything unclear or something is stumping you along this progress.