2011-08-22 14 views
6

私は計算に非常に大規模で高価な配列を持っています。私はその宣言をオプションにしたいと思っていますが、理想的には私のコード全体を書き直す必要はありません。それが今であるかのPythonの遅延変数?または、高価な計算が遅れる

例:私がやりたいものの

x = function_that_generates_huge_array_slowly(0) 
y = function_that_generates_huge_array_slowly(1) 

例:

x = lambda: function_that_generates_huge_array_slowly(0) 
y = lambda: function_that_generates_huge_array_slowly(1) 
z = x * 5 # this doesn't work because lambda is a function 
     # is there something that would make this line behave like 
     # z = x() * 5? 
g = x * 6 

、上記のようにラムダを使用して所望の効果の一つを達成するが - の計算配列は必要になるまで遅延されます。変数 "x"を複数回使用する場合は、毎回計算する必要があります。私は一度だけ計算したいと思う。

EDIT: さらに検索した後、クラス内に「遅延」属性(例:http://code.activestate.com/recipes/131495-lazy-attributes/)を入れたいと思うように見えます。別のクラスを作ることなく、何か似たようなことをする方法はないと思いますか?

EDIT2:私は解決策の一部を実装しようとしているが、私は違いを理解していないので、私が問題に実行しています:

class sample(object): 
    def __init__(self): 
     class one(object): 
      def __get__(self, obj, type=None): 
       print "computing ..." 
       obj.one = 1 
       return 1 
     self.one = one() 

class sample(object): 
    class one(object): 
     def __get__(self, obj, type=None): 
      print "computing ... " 
      obj.one = 1 
      return 1 
    one = one() 

高価な変数はクラスの一部であることが意図されているので、これらのバリエーションが私が探しているものだと思います。

+0

http://stackoverflow.com/questions/5078726/setting-a-property-inside-a-python-methodは私がやろうとしていることに対して怠惰をより効果的に実装します – keflavich

答えて

6

あなたの問題の最初の半分(値を再利用する)は簡単に解決されています

class LazyWrapper(object): 
    def __init__(self, func): 
     self.func = func 
     self.value = None 
    def __call__(self): 
     if self.value is None: 
      self.value = self.func() 
     return self.value 

lazy_wrapper = LazyWrapper(lambda: function_that_generates_huge_array_slowly(0)) 

それでもlazy_wrapper()ないlasy_wrapperとしてそれを使用する必要があります。

あなたはいくつかの変数を何度もアクセスするつもりなら、それを使用する方が速いことがあります

class LazyWrapper(object): 
    def __init__(self, func): 
     self.func = func 
    def __call__(self): 
     try: 
      return self.value 
     except AttributeError: 
      self.value = self.func() 
      return self.value 

をより速く、より遅い最初の呼び出しとその後の用途を作るであろう。

編集:私はあなたがクラスの属性を使用する必要がある同様のソリューションを見つけました。どちらの方法でも、遅延アクセスのたびに書き直す必要があるので、好きなものを選んでください。

編集2:また、行うことができます。

class YourClass(object) 
    def __init__(self, func): 
     self.func = func 
    @property 
    def x(self): 
     try: 
      return self.value 
     except AttributeError: 
      self.value = self.func() 
      return self.value 

あなたは、インスタンスの属性としてxにアクセスしたい場合。追加のクラスは必要ありません。クラスシグネチャを変更したくない場合は(funcが必要になるため、関数呼び出しをプロパティにハードコードすることができます)。クラスを書く

+0

'__call__ 'あなたがプロパティを使用した場合、'() 'を避けることができ、検索/置換が容易になります。これは既存のコードに依存します。 – 9000

+0

検索/置換にどのような違いがあるかわかりません。そしてあなたは '()'を何か他のものに置き換えているだけで、あなたが好む主観的な質問のように思えます。私の方法は、それを実装するための最小のコード方法です。 – agf

+0

私は2番目のレイジーラッパーが好きです...私はそれを撃つでしょう。私が見つけた解決策は、文脈の中で私の頭を包み込むための抽象化の数が多すぎます。 – keflavich

6

がより堅牢ですが、(私はあなたが求めていると思われた)簡単にするために最適化し、私は次の解決策を考え出した:

cache = {} 

def expensive_calc(factor): 
    print 'calculating...' 
    return [1, 2, 3] * factor 

def lookup(name): 
    return (cache[name] if name in cache 
     else cache.setdefault(name, expensive_calc(2))) 

print 'run one' 
print lookup('x') * 2 

print 'run two' 
print lookup('x') * 2 
+0

それは簡単なので、悪い考えではありませんが、私のコードではより多くの書き換えが必要です。上記のLazyWrapperを最初に試してみます。 – keflavich

+0

Gringo - 上記に加えて(同じコード内の2つの異なるユースケース)あなたの提案を使用して終了しましたが、agfの答えは私が求めていた質問に直接答えました。ありがとう! – keflavich

+1

しかし、あなたは特にクラスレスソリューションを求めていました。そのため、私はこれを思いつきました。 defaultdictを使用することは、私が持っていた別のアイデアです、btw。 –

1

あなたは、単純な名前を作ることができない、などのx、実際に遅延的に評価する。名前はハッシュテーブル内のエントリに過ぎません(例:locals()またはglobals()が返されます)。これらのシステムテーブルのアクセス方法にパッチを適用しない限り、単純な名前解決にコードの実行をアタッチすることはできません。

しかし、さまざまな方法でラッパーをキャッシュする機能をラップすることができます。 これは、オブジェクト指向の方法です:これはクラスレスな方法です

class CachedSlowCalculation(object): 
    cache = {} # our results 

    def __init__(self, func): 
     self.func = func 

    def __call__(self, param): 
     already_known = self.cache.get(param, None) 
     if already_known: 
      return already_known 
     value = self.func(param) 
     self.cache[param] = value 
     return value 

calc = CachedSlowCalculation(function_that_generates_huge_array_slowly) 

z = calc(1) + calc(1)**2 # only calculates things once 

:現実の世界では

def cached(func): 
    func.__cache = {} # we can attach attrs to objects, functions are objects 
    def wrapped(param): 
     cache = func.__cache 
     already_known = cache.get(param, None) 
     if already_known: 
      return already_known 
     value = func(param) 
     cache[param] = value 
     return value 
    return wrapped 

@cached 
def f(x): 
    print "I'm being called with %r" % x 
    return x + 1 

z = f(9) + f(9)**2 # see f called only once 

あなたはおそらくLRUアルゴリズムを使用して、適切なサイズにキャッシュを維持するためにいくつかのロジックを追加します。

+0

LRUアルゴリズムとは何ですか?私は問題へのあなたのアプローチが好きです - それは上のGringoに似ていますが、場合によってはよりエレガントかもしれません - しかし、私の問題は実際にはクラス属性によってかなりうまく対処されています。私は実際にグローバル変数 "x"を遅延評価する必要はありません。私は遅れて評価されるためにクラス属性が必要です。あなたの解決策は前者の場合には良いことです。 – keflavich

+0

しかし、好奇心から... locals()dict/hashtableそれ自体は、属性をオーバーライドできるクラスですか?私はそれがひどい考えだと思っていますが、原則として可能ですか? – keflavich

+0

LRU = [Least Recently Used](http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used):キャッシュエントリを作成またはアクセスすると、リストの先頭に置かれます。リストの長さが長くなり過ぎると、最も最近に使用されたエントリは破棄されます。 'locals()'と 'globals()'は両方とも、読み取り専用のメソッドスロットを持つCベースのdictを返します。オーバーライドする機会はありません。 – 9000