2017-05-19 16 views
2

私は&のメモをキャッシュするという概念にかなり新しいです。私は他のいくつかのディスカッション&リソースherehere、およびhereを読んだが、それらすべてをうまくフォローできなかった。Python:新しい関数のパラメータに依存してキャッシュされた関数の結果を呼び出す

クラス内に2つのメンバー関数があるとします。 (以下の簡単な例)最初の関数totalは計算コストが高いと仮定します。第2の関数subtotalは、第1の関数からの戻り値を使用することを除いて、計算上単純です。そのため、結果的に返される結果を得るために現在はtotalを再呼び出しする必要があるため、計算上高価になります。

は、私が最初の関数の結果をキャッシュし、第二の入力としてこれを使用したい、ysubtotalに株式totalの最近の呼び出しへの入力x入力場合。つまり:ytotal
前コール中xの値に等しい小計()を呼び出し、その後、代わり 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よりも何も必要ないようです。 (私はPython 3.5です。)@ unutbuのコメントに関して、私は@lru_cacheでtotal()をデコレートしてもエラーにはなりません。私は私自身の例を修正してみましょう、私は他の初心者のためにここにこれをアップしておこう。この場合

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

PS:lru_cacheをクラスメソッドに適用してエラーが発生したのは間違っていました。ただし、エラーはありませんが、メモリリークの原因となります(http://stackoverflow.com/q/33672412/190597)。 – unutbu

+0

ここで 'self.a'と' self.b'は変更されません。しかし、ありがとう、それは知っておくと便利です。 –

1
Python3.2で

以降、あなたがfunctools.lru_cacheを使用することができます。 totalfunctools.lru_cacheに直接飾る場合、lru_cacheは、両方の引数の値に基づいて、selfxの両方の値に基づいて、戻り値を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) 

プリント:それは最初の1、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 は、スレッドセーフであるということです。には、 長時間実行中のプロセスが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 
関連する問題