lru_cache 为所有 class 个实例保存

lru_cache that saves for all class instances

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

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

我认为这可以通过使用一个变量来保存已经执行的计算来完成。 我稍微修改了您的代码,让我们看一下。 假设您有以下代码:

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 空字典(暂时)。请注意,它是在 class 之外声明的。 其次,我创建了 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

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

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

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

您可以查看以下代码片段(摘自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)

使用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