【问题标题】:Why does functools.lru_cache not cache __call__ while working on normal methods为什么 functools.lru_cache 在处理普通方法时不缓存 __call__
【发布时间】:2019-04-24 16:12:05
【问题描述】:

我一直在尝试使functools.lru_cache 实例具体化,如this answer 中所述,但是当用于__call__ 方法时,他们的解决方案失败了。

class test:
    def __init__(self):
        self.method = lru_cache()(self.method)
        self.__call__ = lru_cache()(self.__call__)

    def method(self, x):
        print('method', end=' ')
        return x

    def __call__(self, x):
        print('__call__', end=' ')
        return x

b = test()
# b.method is cached as expected
print(b.method(1)) # method 1
print(b.method(1)) # 1

# __call__ is executed every time
print(b(1)) # __call__ 1
print(b(1)) # __call__ 1

所以__call__ 的结果在使用此方法包装时不会被缓存。 __call__ 上的缓存甚至不注册已调用的函数,并且不可散列的值不会引发错误。

print(b.method.cache_info())
# CacheInfo(hits=1, misses=1, maxsize=128, currsize=1)
print(b.__call__.cache_info())
# CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)

print(b.call({})) # __call__ {}
print(b.method({})) # ... TypeError: unhashable type: 'dict'

【问题讨论】:

  • 我试过你的代码,如果将 @lru_cache 装饰器应用到 __call__ ,它工作正常,但对于你的情况,你在初始化时将 __call__ 分配给 self ,所以 lru 版本的__call__添加self.__dict__ 中,而实际上并未更改__call__ 方法。
  • @Enix 如果您的意思是通常装饰__call__,那么是的,这适用于一个实例,但是一旦您有两个实例,就会共享缓存,并在一个实例上重置它会在其他实例,因此使用装饰器通常不起作用。 (我尽量不提出关于如何拥有特定于实例的缓存的问题,因为链接的问题涵盖了这一点)

标签: python caching magic-methods


【解决方案1】:

这是由于类属性和实例属性的区别。当访问一个属性(例如method)时,python 首先检查一个实例属性。如果您还没有分配给self.method,它将找不到。然后检查类属性,相当于self.__class__.method。这个函数的值不会通过分配给self.method而改变,只会更新实例属性。

但是,b(1) 变为 b.__class__.__call__(b, 1),它使用 __call__b.__call__(1) 的原始 class 定义,因为它使用了 实例定义。

【讨论】:

    【解决方案2】:

    原来的答案真的很好。

    我附上了该问题的另一种解决方案。 methodtools.lru_cache 将按您的预期工作。

    from methodtools import lru_cache
    
    class test:
        @lru_cache
        def __call__(self, x):
            print('__call__', end=' ')
            return x
    

    需要通过pip安装methodtools

    pip 安装方法工具

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-16
      • 2022-01-11
      • 2021-08-17
      • 1970-01-01
      • 2016-08-23
      • 1970-01-01
      相关资源
      最近更新 更多