2010-04-26 23 views
23

我有一個具有確定性結果的python函數。這需要很長的時間來運行,併產生大量輸出:當函數被修改時,散列python函數以重新生成輸出

def time_consuming_function(): 
    # lots_of_computing_time to come up with the_result 
    return the_result 

我修改time_consuming_function不時,但我想,以避免它再次運行,而它的不變。 [time_consuming_function只依賴於爲了這裏考慮的目的而不可變的函數;即它可能具有來自Python庫的函數,但不是來自我會改變的其他代碼段。]向我暗示的解決方案是緩存輸出並緩存函數的一些「散列」。如果哈希值發生變化,函數將被修改,我們必須重新生成輸出。

這是可能或荒謬的?


更新:根據答案,它看起來像我想要做的是「memoize的」 time_consuming_function,除了代替(或補充)的參數傳遞到一個不變的功能,我想說明一種本身會改變的功能。

+4

+1有趣的問題。 – zdav 2010-04-26 20:46:00

+0

你如何修改該方法?你想在程序運行時保持哈希值,還是在一次運行中,但是在一些模塊重新加載? – doublep 2010-04-26 20:49:13

+0

我會在腳本文件中的方法;我可能會隨時修改它。應用程序是這個函數會產生「問題數據」,用於在某些仿真代碼中運行。我會不時改變這個問題。 – 2010-04-26 21:23:00

回答

6

如果我理解您的問題,我想我會像這樣解決它。這是一種觸動邪惡,但我認爲它比我在這裏看到的其他解決方案更可靠,更有效。

import inspect 
import functools 
import json 

def memoize_zeroadic_function_to_disk(memo_filename): 
    def decorator(f): 
     try: 
      with open(memo_filename, 'r') as fp: 
       cache = json.load(fp) 
     except IOError: 
      # file doesn't exist yet 
      cache = {} 

     source = inspect.getsource(f) 

     @functools.wraps(f) 
     def wrapper(): 
      if source not in cache: 
       cache[source] = f() 
       with open(memo_filename, 'w') as fp: 
        json.dump(cache, fp) 

      return cache[source] 
     return wrapper 
    return decorator 

@memoize_zeroadic_function_to_disk(...SOME PATH HERE...) 
def time_consuming_function(): 
    # lots_of_computing_time to come up with the_result 
    return the_result 
+0

所以唯一的哈希是Python的內部字典鍵哈希,其中鍵是函數的整個未編譯代碼的字符串值。有沒有辦法獲得函數的編譯代碼,所以改變行間距或註釋不會導致不同的值? – 2010-04-27 15:58:52

+0

@Seth,是的,這裏使用Python的內部哈希值是有意義的,因爲你真正想要的是比較值(以免發生哈希碰撞而不知道它,這是不太可能的,但很有可能)。要緩存最近的函數,我不會使用字典或散列,但只是比較值。只是因爲你說(帶外),你想存儲多個版本的功能,所以你可以返回到舊的代碼,我使用的字典。 – 2010-04-27 16:56:46

+0

@Seth,應該可以存儲比整個源代碼更少的信息 - 空格和註釋,並且像這樣完整 - 但需要小心一些,以確保您有足夠的條件進行匹配。 'f.func_code.co_code'是該函數實際存儲的字節碼,但我不確定我可以向你保證它在編譯或不編譯時是相同的。我也不完全確定它不能給你誤報。 – 2010-04-27 16:57:04

1

而不是把函數放在一個字符串中,我會把函數放在它自己的文件中。例如,將其稱爲time_consuming.py。這將是這個樣子:

def time_consuming_method(): 
    # your existing method here 

# Is the cached data older than this file? 
if (not os.path.exists(data_file_name) 
    or os.stat(data_file_name).st_mtime < os.stat(__file__).st_mtime): 
    data = time_consuming_method() 
    save_data(data_file_name, data) 
else: 
    data = load_data(data_file_name) 

# redefine method 
def time_consuming_method(): 
    return data 

在測試基礎設施這個工作,我會註釋掉慢的部分。做一個簡單的函數,只是返回0,讓所有的保存/加載的東西工作到您滿意,然後把慢比特回來。

-1

你所描述的是有效的memoization。通過定義裝飾器可以記憶大多數常用功能。

A(過於簡化的)例子:

def memoized(f): 
    cache={} 
    def memo(*args): 
     if args in cache: 
      return cache[args] 
     else: 
      ret=f(*args) 
      cache[args]=ret 
      return ret 
    return memo 

@memoized 
def time_consuming_method(): 
    # lots_of_computing_time to come up with the_result 
    return the_result 

編輯:

從邁克·格雷厄姆的評論和OP的更新,它現在很清楚,值需要被緩存在程序的不同運行。這可以通過使用緩存的某些持久性存儲來完成(例如,使用Pickle或簡單的文本文件,或者可能使用完整的數據庫,或其中的任何內容)。選擇使用哪種方法取決於OP的需求。其他幾個答案已經爲此提供了一些解決方案,所以我不打算在此重複。

+0

看來,OP想要在運行時間之間記憶一個函數的一個返回值。這基於各種參數在一次運行期間緩存函數的各種返回值。 – 2010-04-26 22:28:58

+0

@Mike Graham:謝謝,我更新了我的答案。 – MAK 2010-04-27 09:25:14

0

所以,這裏是一個非常整潔的使用技巧裝飾:

 
def memoize(f): 
    cache={}; 
    def result(*args): 
     if args not in cache: 
      cache[args]=f(*args); 
     return cache[args]; 
    return result; 

通過以上,那麼你可以使用:

 
@memoize 
def myfunc(x,y,z): 
    # Some really long running computation 

當你調用MYFUNC,你實際上將調用memoized它的版本。漂亮整潔,是吧?當你要重新定義你的函數,只需使用「@memoize」了,或明確寫入:

 
myfunc = memoize(new_definition_for_myfunc); 

編輯
我不知道你想多次運行之間的緩存。在這種情況下,您可以執行以下操作:

 
import os; 
import os.path; 
import cPickle; 

class MemoizedFunction(object): 
    def __init__(self,f): 
     self.function=f; 
     self.filename=str(hash(f))+".cache"; 
     self.cache={}; 
     if os.path.exists(self.filename): 
      with open(filename,'rb') as file: 
       self.cache=cPickle.load(file); 

    def __call__(self,*args): 
     if args not in self.cache: 
      self.cache[args]=self.function(*args); 
     return self.cache[args]; 

    def __del__(self): 
     with open(self.filename,'wb') as file: 
       cPickle.dump(self.cache,file,cPickle.HIGHEST_PROTOCOL); 

def memoize(f): 
    return MemoizedFunction(f); 
+0

非常整齊,是的,但沒有回答這個問題。重點是方法中的代碼改變了,而不是它的輸入參數。 – 2010-04-26 21:40:55

+0

看來,OP想要在運行時間之間記憶一個函數的一個返回值。這基於各種參數在一次運行期間緩存函數的各種返回值。 – 2010-04-26 22:26:07

+0

@Mike,好的。我沒有意識到這是程序運行之間的事情。 – 2010-04-26 23:07:13

0

第一部分是您的查找表的記憶和序列化。這應該足夠簡單,基於一些python序列化庫。第二部分是,當源代碼更改時,您想要刪除序列化的查找表。也許這正在被推翻成一些奇特的解決方案。大概當你更改代碼時,你在某處檢查它?爲什麼不在你的簽入例程中添加一個鉤子來刪除你的序列化表?或者,如果這不是研究數據並且正在生產中,請將其作爲發佈過程的一部分,如果文件的修訂版號(將此函數放入其自己的文件中)已更改,則您的發行腳本將刪除已序列化的查找表。