可重置的 Python 备忘装饰器(针对某个实例)

1 投票
2 回答
729 浏览
提问于 2025-04-17 08:55

这个问题是对python 可重置的实例方法缓存装饰器答案的进一步讨论。实际上,我本来想把这个写成对那个答案的评论,但我现在的声望还不够(希望以后能有)。

在那个答案中,@aix 提出了一个很好的方法,使用装饰器来重置缓存的函数。

不过,那个答案的“问题”在于,当你调用 reset 方法时,它会重置所有实例的缓存。用 @aix 定义的同样的类来举个例子,应该能让这个问题更清楚:

c = my_class()
print c.my_func(55)
# This time the function is computed and stored in cache
print c.my_func(55)
# This second call is cached...  no computation needed
d = my_class()
d.my_func.reset()
print c.my_func(55)
# This third call is also computed, since the cache has been cleared

我认为 d.my_func.reset() 应该只清除 d.my_func 的预计算值缓存,而不是清除所有 my_class 的其他实例的缓存。

我有一个半成品的解决方案,虽然不太令人信服,但我想可能有人能改进它。

我修改了 reset() 方法,并引入了一个参数 instance

  def _reset(self,instance):
  for cached in self.cache.keys():
      if cached[0] == instance:
          del self.cache[cached] 

现在如果我这样做:

c = my_class()
print c.my_func(55)
# This time the function is computed and stored in cache
print c.my_func(55)
# This second call is cached
d = my_class()
d.my_func.reset(d)
print c.my_func(55)
# Now this third call is cached

不过,调用重置方法的方式 d.my_func.reset(d) 看起来(至少)有点丑,但我还没找到更好的解决方案……有没有人有什么想法?

谢谢!

编辑

为了记录:与其把实例作为参数传递,你也可以通过修改装饰器的 __get__ 方法来实现相同的效果。

__get__(self, obj, objtype) 方法中添加 self.original_self = obj,然后在 _reset 方法中把 if cached[0] == instance 替换为 if cached[0] == self.original_self。这样就解决了问题!

2 个回答

0

有两种可能性 - 它们会把数据存储在实例的 __dict__ 里,而不是放在一个全局的备忘录字典里:

def MemoMeth(meth):
    """Memoizer for class methods (uses instance __dict__)
    Example:
    class C:
        @MemoMeth
        def slowmeth(self, a, b): ...
    """
    class _MemoMeth:
        def __init__(self, this):
            self.this = this
        def __call__(self, *args):
            memo = self.this.__dict__.setdefault(_MemoMeth, {})
            try:
                return memo[args][1]
            except:
                memo[args] = tr = time.time(), meth(self.this, *args)
                return tr[1]
        def reset(self):
            self.this.__dict__.setdefault(_MemoMeth, {}).clear()
    return property(_MemoMeth)

这一种使用了线程本地存储,而不是为每次访问创建一个新的包装对象:

def MemoMeth3(meth):
    """Memoizer for class methods (uses instance __dict__)
    Example:
    class C:
        @MemoMeth3
        def slowmeth(self, a, b): ...
    """
    tls = threading.local()
    class MemoProperty(object):
        def __call__(self, *args):
            memo = tls.instance.__dict__.setdefault(self, {})
            try:
                return memo[args][1]
            except:
                memo[args] = tr = time.time(), meth(tls.instance, *args)
                return tr[1]
        def reset(self):
            tls.instance.__dict__.setdefault(self, {}).clear()
        def __get__(self, instance, owner):
            tls.instance = instance
            return self
    return MemoProperty()
0

你可以使用方法的 __self__ 属性(在这里就是 self.__self__)来找到它绑定的类实例,这样就不用再手动传递这个实例了。

撰写回答