2017-05-19 5 views
2

나는 메모 작성을 & 캐싱의 개념에 상당히 새로운입니다. 다른 토론 인 & 리소스 here, herehere을 읽었지 만 잘 수행하지 못했습니다.Python : 새로운 함수 매개 변수에 따라 캐시 된 함수 결과를 호출하십시오.

클래스 내에 두 개의 멤버 함수가 있다고 가정 해보십시오. (아래의 간단한 예제.) 첫 번째 함수 total이 계산 상 비싸다고 가정합니다. 두 번째 함수 subtotal은 첫 번째 함수의 반환 값을 사용한다는 점을 제외하고는 계산 상 간단합니다. 반환 된 결과를 얻으려면 현재 total을 다시 호출해야하기 때문에 연산 비용이 많이 듭니다.

내가 첫 번째 함수의 결과를 캐시하고 두 번째에 입력로 사용하려면, ysubtotal에 주 total의 최근 통화에 대한 입력 x 입력 경우. 즉 : ytotal
이전 통화 x의 값과 동일 합계()를 호출하고 대신 total 재 호출
의 캐싱 결과를 사용

  • 경우.
  • 그렇지 않으면 total()x = y으로 호출하면됩니다.

예 :

class MyObject(object): 

    def __init__(self, a, b): 
     self.a, self.b = a, b 

    def total(self, x): 
     return (self.a + self.b) * x  # some time-expensive calculation 

    def subtotal(self, y, z): 
     return self.total(x=y) + z  # Don't want to have to re-run total() here 
             # IF y == x from a recent call of total(), 
             # otherwise, call total(). 
+0

시도해 보셨습니까? http://stackoverflow.com/a/18723434/2570677 나는 내 코드에서 그것을 사용하고 완벽하게 작동합니다. –

+0

'@ functools.lru_cache'라고 가정하고 계신가요? –

+0

에서 링크 된 리소스를 통해 기본 캐시 기능으로'total'을 꾸미는 것을 막을 수 있습니까? '@ functools.lru_cache (maxsize = N)'을 넣으면 같은 인수에 대해'N' 결과가 캐시됩니다. 왜 당신 시나리오에서 그렇게되지 않을까요? –

답변

1

답장을 보내 주셔서 감사합니다. 그 내용을 읽고 후드에서 진행되는 작업을 확인하는 것이 도움이되었습니다. @ Tadhg McDonald-Jensen이 말했듯이, @functools.lru_cache보다 더 많은 것을 필요로하지 않는 것 같습니다. (저는 파이썬 3.5에 있습니다.) @ unutbu의 코멘트와 관련하여, total()을 @lru_cache으로 꾸미는데 오류가 없습니다. 내 자신의 예를 해결하자 본인은 초보자를위한 여기를하겠습니다 :

from functools import lru_cache 
from datetime import datetime as dt 

class MyObject(object): 
    def __init__(self, a, b): 
     self.a, self.b = a, b 

    @lru_cache(maxsize=None) 
    def total(self, x):   
     lst = [] 
     for i in range(int(1e7)): 
      val = self.a + self.b + x # time-expensive loop 
      lst.append(val) 
     return np.array(lst)  

    def subtotal(self, y, z): 
     return self.total(x=y) + z  # if y==x from a previous call of 
             # total(), used cached result. 

myobj = MyObject(1, 2) 

# Call total() with x=20 
a = dt.now() 
myobj.total(x=20) 
b = dt.now() 
c = (b - a).total_seconds() 

# Call subtotal() with y=21 
a2 = dt.now() 
myobj.subtotal(y=21, z=1) 
b2 = dt.now() 
c2 = (b2 - a2).total_seconds() 

# Call subtotal() with y=20 - should take substantially less time 
# with x=20 used in previous call of total(). 
a3 = dt.now() 
myobj.subtotal(y=20, z=1) 
b3 = dt.now() 
c3 = (b3 - a3).total_seconds() 

print('c: {}, c2: {}, c3: {}'.format(c, c2, c3)) 
c: 2.469753, c2: 2.355764, c3: 0.016998 
+0

'self.a'와'self.b'는 절대로 바뀌나요? 그렇다면 '전체'의 계산 된 값이 변경되므로 캐시 된 값을 지워야합니다. setter가 ['total.cache_clear()'] (http://stackoverflow.com/a/37654201/190597)를 호출하는'a' 및'b' 설정 가능한 속성을 만들어 구현할 수 있습니다. – unutbu

+0

추신 : 오류를 발생시키는 클래스 메소드에 lru_cache를 적용하는 것에 대해 잘못된 것이 었습니다. 오류는 없지만 [메모리 누수가 발생합니다] (http://stackoverflow.com/q/33672412/190597). – unutbu

+0

여기서'self.a'와'self.b'는 수정되지 않습니다. 하지만 고마워, 알고있는 것이 도움이된다. –

1
Python3.2와

이상, 당신은 functools.lru_cache를 사용할 수 있습니다. totalfunctools.lru_cache으로 직접 꾸밀 경우 lru_cacheselfx 두 값의 값을 기반으로 total의 반환 값을 캐시합니다. lru_cache의 내부 dict는 self에 대한 참조를 저장하기 때문에 @lru_cache를 클래스 메소드에 직접 적용하면 self에 대한 순환 참조가 생성되어 클래스의 인스턴스를 참조 해제 할 수 없게 (따라서 메모리 누수) 만듭니다.

import functools 
import weakref 

def memoized_method(*lru_args, **lru_kwargs): 
    """ 
    https://stackoverflow.com/a/33672499/190597 (orly) 
    """ 
    def decorator(func): 
     @functools.wraps(func) 
     def wrapped_func(self, *args, **kwargs): 
      # We're storing the wrapped method inside the instance. If we had 
      # a strong reference to self the instance would never die. 
      self_weak = weakref.ref(self) 
      @functools.wraps(func) 
      @functools.lru_cache(*lru_args, **lru_kwargs) 
      def cached_method(*args, **kwargs): 
       return func(self_weak(), *args, **kwargs) 
      setattr(self, func.__name__, cached_method) 
      return cached_method(*args, **kwargs) 
     return wrapped_func 
    return decorator 


class MyObject(object): 

    def __init__(self, a, b): 
     self.a, self.b = a, b 

    @memoized_method() 
    def total(self, x): 
     print('Calling total (x={})'.format(x)) 
     return (self.a + self.b) * x 


    def subtotal(self, y, z): 
     return self.total(x=y) + z 

mobj = MyObject(1,2) 
mobj.subtotal(10, 20) 
mobj.subtotal(10, 30) 

인쇄 : 그것은 첫 번째, self 이외의 모든 인수에 따라 결과를 캐시하고, 순환 참조 문제를 방지하기 위해 weakref를 사용합니다 - 당신은 클래스 메소드와 lru_cache을 사용할 수 있습니다

Here is a workaround

Calling total (x=10) 

한 번만.


또는이 당신이 딕셔너리를 사용하여 자신의 캐시를 롤 수있는 방법입니다 :이 딕셔너리 기반 캐시를 통해 lru_cache

class MyObject(object): 

    def __init__(self, a, b): 
     self.a, self.b = a, b 
     self._total = dict() 

    def total(self, x): 
     print('Calling total (x={})'.format(x)) 
     self._total[x] = t = (self.a + self.b) * x 
     return t 

    def subtotal(self, y, z): 
     t = self._total[y] if y in self._total else self.total(y) 
     return t + z 

mobj = MyObject(1,2) 
mobj.subtotal(10, 20) 
mobj.subtotal(10, 30) 

장점 중 하나는 lru_cache 스레드 안전 것입니다.lru_cache에는 장기 실행 프로세스가 total을 여러 번 호출하여 다른 값이 x 인 여러 번 발생하기 때문에 은 바운드없이 메모리 사용을 방지하는 데 도움이되는 maxsize 매개 변수가 있습니다.

0

을 나는 간단한 일을 할 것이다이 경우, 어쩌면 가장 우아한 방법이 아니라, 문제에 대한 작동합니다

class MyObject(object): 
    param_values = {} 
    def __init__(self, a, b): 
     self.a, self.b = a, b 

    def total(self, x): 
     if x not in MyObject.param_values: 
      MyObject.param_values[x] = (self.a + self.b) * x 
      print(str(x) + " was never called before") 
     return MyObject.param_values[x] 

    def subtotal(self, y, z): 
     if y in MyObject.param_values: 
      return MyObject.param_values[y] + z 
     else: 
      return self.total(y) + z