Python 装饰器、类方法与评估 -- django memoize
我有一个可以正常工作的记忆装饰器,它使用Django的缓存系统来记住一个函数的结果,保存一段时间。我特别把它应用在一个类的方法上。
我的装饰器看起来像这样:
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
我在一个类的方法上使用这个装饰器,所以像这样:
class StorageUnit(models.Model):
@memoize(timeout=60*180, keygenfunc=lambda x,y: str(x[0].id))
def someBigCalculation(self):
...
return result
实际的记忆过程运行得非常好!也就是说,调用
myStorageUnitInstance.someBigCalculation()
可以正确地使用缓存。好的,太棒了!
我的问题是,当我想手动清除某个特定实例的缓存时,我希望能够运行
myStorageUnitInstance.someBigCalculation.invalidate()
然而,这样做不行,因为“self”没有被传入,所以键没有生成。我得到了一个“IndexError: tuple index out of range”的错误,指向我之前提到的lambda函数。
当然,我可以成功调用:
myStorageUnitInstance.someBigCalculation.invalidate(myStorageUnitInstance)
这完全没问题。但是,当我已经在引用一个特定实例时,这样做让我觉得有点多余。我该如何让Python把这个当作一个实例方法来处理,从而正确填充“self”变量呢?
2 个回答
虽然我同意AlexM的观点,但我有一些闲暇时间,觉得这个问题挺有意思的:
# from django.whereever import cache
class memoize(object):
def __init__(self,prefix='mysite', timeout=300, keygenfunc=None):
class memo_descriptor(object):
def __init__(self,func):
self.func = func
def __get__(self,obj,klass=None):
key = prefix + '___' + self.func.func_name + '___' + keygenfunc(obj)
class memo(object):
def __call__(s,*args,**kwargs):
rv = cache.get(key)
if rv is None:
rv = self.func(obj,*args, **kwargs)
cache.set(key, rv, timeout)
return rv
def invalidate(self):
cache.set(key, None, 1)
return memo()
self.descriptor = memo_descriptor
def __call__(self,func):
return self.descriptor(func)
注意,我把keygenfunc
的定义从(*args,**kwargs)
改成了(instance)
,因为你在例子中是这样使用的。如果你是根据方法调用的参数来生成一个键,而不是根据对象实例,那就不可能让someBigCalculation.invalidate
这个方法以你想要的方式清除缓存。
class StorageUnit(models.Model):
@memoize(timeout=60*180, keygenfunc=lambda x: str(x.id))
def someBigCalculation(self):
return 'big calculation'
这段代码里有很多内容,所以它是否真的能让你的生活更轻松,这一点值得考虑。
描述符(Descriptors)必须总是设置在类上,而不是实例上(具体细节可以参考这份指南)。当然,在这种情况下,你甚至不是在实例上设置它,而是在另一个函数上设置(然后作为绑定方法的一个属性来获取)。我认为,想要使用你想要的语法,唯一的方法就是让funcwrap成为一个自定义类的实例(这个类必须是描述符类,也就是说,需要定义合适的__get__
方法,就像函数本身那样)。然后invalidate
可以成为那个类的方法(或者,也许更好的是,成为另一个自定义类的方法,这个类的实例是由前面提到的描述符类的__get__
方法产生的“类似绑定方法的东西”),最终可以达到你想要的im_self
(在绑定方法中是这样命名的)。
为了你所追求的小便利,付出的代价(无论是概念上还是编码上)都相当高——高到我真的不想花一两个小时来完全开发和测试它。不过,我希望我给出的指引足够清晰,如果你仍然想继续这个方向,我会很乐意帮助你,解答任何不清楚的地方或者让你感到困惑的问题。