lru_cache, который сохраняет для всех экземпляров классов - PullRequest
0 голосов
/ 24 октября 2018

Можно ли как-нибудь lru_cache a @property на уровне класса в python, чтобы даже при возврате этого вычисляемого свойства с теми же значениями для другого экземпляра класса свойство не было пересчитано, а скорее извлечено из кэша.Я хочу добиться чего-то вроде следующего:

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

Ответы [ 4 ]

0 голосов
/ 05 мая 2019

Решение с использованием 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
0 голосов
/ 24 октября 2018

Итак, я нашел решение, хотя и не с использованием 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
0 голосов
/ 24 октября 2018

Я добавляю второй ответ, потому что у меня недостаточно репутации, чтобы добавить комментарий к вашему ответу.

Вы можете проверить следующие фрагменты кода (взяты из 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)
0 голосов
/ 24 октября 2018

Я думаю, что это можно сделать, используя переменную для хранения уже выполненных вычислений.Я немного переработал ваш код, поэтому давайте посмотрим на него.Предположим, у вас есть следующий код:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...