Python装饰器和类方法及评估 - django memoize

时间:2009-11-12 02:32:57

标签: python class scope methods decorator

我有一个工作的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 个答案:

答案 0 :(得分:2)

必须始终在类上设置描述符,而不是在实例上设置描述符(有关所有详细信息,请参阅the how-to guide)。当然,在这种情况下,您甚至不在实例上设置它,而是在另一个函数上设置它(并将其作为绑定方法的属性获取)。我认为使用你想要的语法的唯一方法是使funcwrap成为自定义类的实例(当然,哪个类必须是描述符类,即定义适当的__get__方法,就像函数本身一样做)。然后invalidate可以是该类的一个方法(或者更好的是,另一个自定义类,其实例是由前面提到的描述符类__get__方法生成的“绑定方法类物质”),并最终达到你渴望的im_self(这就是它在命名方法中命名的方式)。

为你寻求的轻微便利付出相当大的代价(概念和编码;-)价格 - 足够大,我真的不想花一两个小时完全开发它并测试它。但是,如果你仍然热衷于此,我希望我已经给你足够明确的指示让你继续前进,事实上,如果有什么不清楚或者有什么事情让你难以理解,我会很乐意澄清并帮助你。

答案 1 :(得分:-1)

虽然我同意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'

该代码中存在很多内容,因此需要考虑的是它是否真正让您的生活更轻松。