2016-09-23 74 views
0

我最近嘗試過Googleogo foo.bar challenge。在我的時間到了之後,我決定嘗試找到解決方案來解決我無法做到的問題,並找到了解決方案here(如果您有興趣,請提供問題說明)。我以前一直在爲我想要緩存的每個函數製作字典,但看起來在這個解決方案中,任何函數/輸入都可以使用相同的語法進行緩存。使用* args和lambda函數在python中緩存

首先,我很困惑代碼是如何工作的,* args變量沒有作爲參數輸入(並且沒有輸出)。這裏有一個改進小例子來說明我的困惑:

mem = {} 

def memoize(key, func, *args): 
    """ 
    Helper to memoize the output of a function 
    """ 

    print(args) 

    if key not in mem: 
     # store the output of the function in memory 
     mem[key] = func(*args) 

    return mem[key] 

def example(n): 
    return memoize(
     n, 
     lambda: longrun(n), 
    ) 

def example2(n): 
    return memoize(
     n, 
     longrun(n), 
    ) 

def longrun(n): 
    for i in range(10000): 
     for j in range(100000): 
      2**10 
    return n 

這裏我用同樣的memoize的功能,但打印。函數示例返回memoize(n,一個lambda函數,)。函數longrun僅僅是一個標識函數,它有很多無用的計算,因此很容易看出緩存是否正常工作。(示例(2)第一次需要約5秒鐘,幾乎是瞬間)。

這裏是我的困惑:

  • 爲什麼memoize的第三個參數是空的?當打印參數記憶它打印()。但不知何故mem [key]將func(* args)存儲爲func(key)?
  • 爲什麼這種行爲僅在使用lambda函數時才起作用(示例將緩存但示例2不會)?我認爲lambda:longrun(n)只是給出一個返回longrun(n)的函數的簡短方法。

作爲一個獎勵,有沒有人知道如何使用裝飾器來記憶函數?

另外我想不出一個更具描述性的標題,歡迎編輯。謝謝。

+0

看一看https://docs.python.org/3/library/functools.html#functools.lru_cache – janbrohl

+0

請參閱文檔和使用搜索。 ['* args'符號提供可變參數](https://docs.python.org/3/tutorial/controlflow.html#arbitrary-argument-lists)。由於您不提供任何參數,因此'* args'是空的。 'example2'不起作用,因爲你沒有提供一個*函數*,所以你提供了調用一個函數*的結果。它應該讀'memoize(n,longrun,n)'。 – MisterMiyagi

+0

感謝@janbrohl,正是我在裝修後所做的一切! – HBeel

回答

2

符號*args表示可變數量的位置參數。例如,print可以用作print(1),print(1, 2),print(1, 2, 3)等。同樣,**kwargs表示可變數目的關鍵字參數。

請注意名稱argskwargs只是一個約定 - 它是***符號,使它們可變。

不管怎麼說,memoize使用此接受基本任何輸入FUNC。如果func的結果未被緩存,則使用參數調用它。在功能調用中,*args基本上與*args在功能定義中相反。例如,下面的是等價的:

# provide *args explicitly 
print(1, 2, 3) 
# unpack iterable to *args 
arguments = 1, 2, 3 
print(*arguments) 

如果args是空的,然後調用print(*args)是與調用print() - 沒有參數傳遞給它。


函數和lambda函數是在python同一。這僅僅是創建函數對象的一種不同的符號。

問題是,在example2,你沒有傳遞函數。你調用一個函數,然後傳遞它的結果。相反,你必須分別傳遞函數和它的參數。

def example2(n): 
    return memoize(
     n, 
     longrun, # no() means no call, just the function object 
     # all following parameters are put into *args 
     n 
    ) 

現在,一些實施細節:爲什麼args空的,爲什麼會出現一個單獨的密鑰?

  • 空的args來自您的lambda定義。讓我們寫爲澄清的函數:

    def example3(n): 
        def nonlambda(): 
         return longrun(n) 
        return memoize(n, nonlambda) 
    

    注意如何nonlambda需要沒有參數。參數n從包含範圍中作爲閉包,bound from the containing scope被綁定。因此,您不必將它傳遞給記憶 - 它已經綁定在nonlambda之內。因此,args在記憶中爲空,即使longrun確實收到參數,因爲兩者不直接交互。

  • 現在,爲什麼它是mem[key] = f(*args)而不是mem[key] = f(key)?這實際上是一個錯誤的問題;正確的問題是「爲什麼不是mem[f, args] = f(*args)?」。

    記憶工作,因爲相同的功能相同的輸入導致相同的輸出。即,f, args標識了您的輸出。理想情況下,您的key將是f, args,因爲這是唯一的相關信息。

    問題是你需要一種方法在mem內查找fargs。如果您曾嘗試將list放入dict之內,那麼您知道有些類型在映射(或任何其他合適的查找結構)中不起作用。所以如果你定義了key = f, args,你不能記憶採用可變/不可變類型的函數。 Python的functools.lru_cache實際上有這個限制。

    定義明確的key是解決此問題的一種方法。它的優點是,主叫方可以選擇一個合適的密鑰,例如在不做任何修改的情況下采取n。這提供了最佳的優化潛力。然而,它很容易中斷 - 僅僅使用n就會漏掉實際調用的函數。使用相同的輸入記憶第二個函數會破壞緩存。

    還有其他的方法,各有利弊。通常是類型的明確轉換:listtuplesetfrozenset等等。這很慢,但最精確。另一種方法是撥打strrepr,如key = repr((f, args, sorted(kwargs.items()))),但它依賴於具有合適的repr的每個值。

+0

感謝您的回答。我明白什麼*參數的含義,我的困惑來自這樣一個事實,即當示例被調用時,它被賦予*參數爲空。字典應該存儲mem [key] = f(key),但它看起來像它的存儲mem [key] = f()(它甚至不應該用於longrun)。我沒有料想到會看到 高清例如(N): 回報memoize的( N, 拉姆達:龍潤(N), ñ )(就像你如何證明例題,這沒有最後的說法太工作。這是我的問題,如何在沒有最後一個參數的情況下繼續工作?) – HBeel

+0

感謝您指出example2中的問題,我錯過了! – HBeel

+0

@HBeel我爲'f()'和'key'添加了一個解釋。總之,'example'不會傳遞'longrun'(需要一個參數),而是'lambda'函數(不需要參數)。 – MisterMiyagi