2011-08-22 45 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」,它必須每次計算。我只想計算一次。

編輯: 經過一些額外的搜索,看起來有可能做到我想要的(大約)在一個類中使用「懶」屬性(例如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

我喜歡第二個LazyWrapper ......我會給它一個鏡頭。我發現的解決方案有太多的抽象層,因此我無法將自己的頭圍繞在上下文中。 – 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 - 我結束了除了上面的使用你的建議(在同一代碼中的兩個不同的用例),但agf的答案更直接地回答了我問的問題。謝謝! – keflavich

+1

好的,但你特別要求一個無類別的解決方案,這就是爲什麼我想出了這個。使用defaultdict是我的另一個想法,順便說一句。 –

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()字典/散列表本身可以有一個屬性被覆蓋的類?我認爲這將是一個可怕的想法,但原則上可以嗎? – keflavich

+0

LRU = [最近最少使用](http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used):一旦你創建或訪問一個緩存條目,你就把它放到列表的開頭;如果列表過長,最近最少使用的條目將被丟棄。 'locals()'和'globals()'都會返回一個帶有隻讀方法槽的基於C的字典,不會被覆蓋。 – 9000