【问题标题】:lru_cache that saves for all class instanceslru_cache 为所有类实例保存
【发布时间】:2019-03-28 18:20:35
【问题描述】:

我有什么办法可以在 python 的类级别 lru_cache@property ,这样即使在为另一个类实例返回具有相同值的计算属性时,也不会重新计算该属性,而是从缓存中提取该属性。我想实现以下目标:

class SomeClass:
    def __init__(self, num1, num2):
         self.num1 = num1
         self.num2 = num2

    @property
    @lru_cache
    def sum(self):  #this can even be a class method if needed, I don't really care
       return self.num1 + self.num2


    t1 = SomeClass(2,3)
    t1.sum
    >> 5          #calculating


    t2 = SomeClass(2,3)
    t2.sum
    >> 5          #returning cache from t1, NOT calculating

【问题讨论】:

    标签: python-3.x class caching properties functools


    【解决方案1】:

    我认为这可以使用一个变量来保存已经执行的计算。 我已经对您的代码进行了一些修改,所以让我们来看看它。 假设您有以下代码:

    CACHED_VALUES = {}
    
    class SomeClass:
      def __init__(self, num1, num2):
        self.num1 = num1
        self.num2 = num2
        self.pair = '{},{}'.format(num1,num2)
        if self.pair not in CACHED_VALUES.keys():
          print('Calculating from init...')
          CACHED_VALUES[self.pair] = self.sum
        else:
          print('Already stored... Getting the values from cache...')
    
      @property
      def sum(self):  #this can even be a class method if needed, I don't really care
          return self.num1 + self.num2
    
    
    t1 = SomeClass(2,3)
    print(t1.sum)
    
    t2 = SomeClass(2,3)
    print(t2.sum)
    
    print('This is new instance.')
    t3 = SomeClass(2,3)
    print(t3.sum)
    

    首先,我创建了空的 CACHED_VALUES 字典(暂时)。请注意,它是在类之外声明的。 其次,我创建了self.pair 变量,它将两个数字都表示为字符串,用逗号分隔。这样做的原因是因为您不能拥有 列表为 字典键。这就是我们将两个数字连接成字符串的原因。

    如果我们应用这种方法,CACHED_VALUES 字典将更新如下:

    CACHED_VALUES = {}
    t1 = SomeClass(2,3)
    print(CACHED_VALUES)
    >> {'2,3': 5}
    

    现在介绍__init__ 方法。

    我添加了 if 条件来检查 CACHED_VALUES 字典是否已经包含计算值。如果不是,则执行函数并将返回值保存到字典中。如果它存在 - 那么我们得到已经计算的值,省略函数执行。

    您可以在下面看到修改后的代码及其输出:

       CACHED_VALUES = {}
    
    class SomeClass:
      def __init__(self, num1, num2):
        self.num1 = num1
        self.num2 = num2
        self.pair = '{},{}'.format(num1,num2)
        if self.pair not in CACHED_VALUES.keys():
          print('Calculating from init...')
          CACHED_VALUES[self.pair] = self.sum
        else:
          print('Already stored... Getting the values from cache...')
    
      @property
      def sum(self):  #this can even be a class method if needed, I don't really care
          return self.num1 + self.num2
    
    
    print('[X]Creating first instance')
    t1 = SomeClass(2,3)
    print(t1.sum)
    
    print('[X]Creating second instance')
    t2 = SomeClass(2,3)
    print(t2.sum)
    
    print('[X]This is instance with different values.')
    t3 = SomeClass(5,7)
    print(t3.sum)
    
    print('[X]This is second instance with different values.')
    t4 = SomeClass(5,7)
    print(t4.sum)
    
    
    
    # OUTPUT:
    [X]Creating first instance
    Calculating from init...
    5
    [X]Creating second instance
    Already stored... Getting the values from cache...
    5
    [X]This is instance with different values.
    Calculating from init...
    12
    [X]This is second instance with same values.
    Already stored... Getting the values from cache...
    12
    

    【讨论】:

    • 我的意思是这是一种解决方法。然而,我找到了另一个使用 @ring.dict({}) 的解决方案。
    【解决方案2】:

    于是我找到了一个解决方案,虽然不是用funtools.lru_cache,而是ring。根据我的阅读,lru_cache 无法做到这一点,但如果有人知道使用lru_cache 的解决方案,请随时发布,我会将其标记为正确答案。

    import ring 
    
    class SomeClass:
        def __init__(self, num1, num2):
             self.num1 = num1
             self.num2 = num2
    
        @ring.dict({})
        @property
        def sum(self): 
           return self.num1 + self.num2
    

    【讨论】:

      【解决方案3】:

      我正在添加第二个答案,因为我没有足够的声誉来为您的答案添加评论。

      可以查看以下代码sn-ps(取自https://docs.python.org/3/library/functools.html):

      @lru_cache(maxsize=None)
      def fib(n):
          if n < 2:
              return n
          return fib(n-1) + fib(n-2)
      
      >>> [fib(n) for n in range(16)]
      [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]
      
      >>> fib.cache_info()
      CacheInfo(hits=28, misses=16, maxsize=None, currsize=16)
      

      @lru_cache(maxsize=32)
      def get_pep(num):
          'Retrieve text of a Python Enhancement Proposal'
          resource = 'http://www.python.org/dev/peps/pep-%04d/' % num
          try:
              with urllib.request.urlopen(resource) as s:
                  return s.read()
          except urllib.error.HTTPError:
              return 'Not Found'
      
      >>> for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
      ...     pep = get_pep(n)
      ...     print(n, len(pep))
      
      >>> get_pep.cache_info()
      CacheInfo(hits=3, misses=8, maxsize=32, currsize=8)
      

      【讨论】:

        【解决方案4】:

        使用methodtools.lru_cache的解决方案

        from methodtools import lru_cache
        
        called = 0
        
        class SomeClass:
            def __init__(self, num1, num2):
                self.num1 = num1
                self.num2 = num2
        
            @lru_cache()
            @classmethod
            def _sum(cls, num1, num2):
                global called
                called += 1
                return num1 + num2
        
            @property
            def sum(self):
                return self._sum(self.num1, self.num2)
        
        
        if __name__ == '__main__':
            assert called == 0
        
            t1 = SomeClass(2, 3)
            print(t1.sum)
            assert called == 1
        
            t2 = SomeClass(2, 3)
            print(t2.sum)
            assert called == 1
        

        【讨论】:

          猜你喜欢
          • 2021-12-17
          • 2018-12-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-01-24
          • 1970-01-01
          相关资源
          最近更新 更多