2013-05-09 88 views
17

有沒有辦法將一個函數的輸出記憶到磁盤?記憶到磁盤 - 蟒蛇 - 永久記憶

我有一個函數

def getHtmlOfUrl(url): 
    ... # expensive computation 

,很想做一些事情,如:

def getHtmlMemoized(url) = memoizeToFile(getHtmlOfUrl, "file.dat") 

,然後調用getHtmlMemoized(URL),以便爲每個URL做昂貴的計算只有一次。

+1

只是pickle(或使用json)緩存字典。 – root 2013-05-09 14:02:34

+1

謝謝,但我是一個蟒蛇新手(第二天)。我沒有絲毫的想法,你的意思是... – seguso 2013-05-09 14:04:05

+1

好,所以你作爲一個新手做什麼是在谷歌查找「pickle python」,並回來給我們,如果你有任何問題。 – 2013-05-09 14:04:55

回答

20

Python提供了一個非常優雅的方式來做到這一點s - 裝飾者。基本上,裝飾器是一種函數,它包裝另一個函數以提供附加功能而不更改函數源代碼。你的裝飾可以這樣寫:

import json 

def persist_to_file(file_name): 

    def decorator(original_func): 

     try: 
      cache = json.load(open(file_name, 'r')) 
     except (IOError, ValueError): 
      cache = {} 

     def new_func(param): 
      if param not in cache: 
       cache[param] = original_func(param) 
       json.dump(cache, open(file_name, 'w')) 
      return cache[param] 

     return new_func 

    return decorator 

一旦你得到了,用@ -syntax「裝飾」的功能,你準備好了。

@persist_to_file('cache.dat') 
def html_of_url(url): 
    your function code... 

注意,該裝飾被有意簡化,可能不適用於所有情況,例如,當源函數接受或返回不能JSON序列化的數據。

更多關於裝飾:How to make a chain of function decorators?

下面是如何使裝飾保存緩存只有一次,在退出時間:

import json, atexit 

def persist_to_file(file_name): 

    try: 
     cache = json.load(open(file_name, 'r')) 
    except (IOError, ValueError): 
     cache = {} 

    atexit.register(lambda: json.dump(cache, open(file_name, 'w'))) 

    def decorator(func): 
     def new_func(param): 
      if param not in cache: 
       cache[param] = func(param) 
      return cache[param] 
     return new_func 

    return decorator 
+3

每次更新緩存時都會寫入一個新文件 - 取決於使用情況,這可能會(或可能不會)打敗加速你從記憶中獲得...... – root 2013-05-09 14:58:14

+1

它*還*包含一個非常好的競爭條件,如果這個裝飾器同時使用,或者(更可能)以可重入的方式使用。如果'a()'和'b()'都被記憶,並且'a()'調用'b()',緩存可以被讀取爲'a()',然後再' ,第一個b的結果會被記憶,但是從調用過來的舊緩存會覆蓋它,b對緩存的貢獻將會丟失。 – SingleNegationElimination 2013-05-09 15:03:58

+0

@root:當然,'atexit'可能是刷新緩存的好地方。另一方面,增加過早的優化可能會破壞此代碼的教育目的。 – georg 2013-05-09 15:25:27

0

這樣的事情應該做的事:

import json 

class Memoize(object): 
    def __init__(self, func): 
     self.func = func 
     self.memo = {} 

    def load_memo(filename): 
     with open(filename) as f: 
      self.memo.update(json.load(f)) 

    def save_memo(filename): 
     with open(filename, 'w') as f: 
      json.dump(self.memo, f) 

    def __call__(self, *args): 
     if not args in self.memo: 
      self.memo[args] = self.func(*args) 
     return self.memo[args] 

基本用法:

your_mem_func = Memoize(your_func) 
your_mem_func.load_memo('yourdata.json') 
# do your stuff with your_mem_func 

如果你想使用它後寫你「緩存」到一個文件 - 需要重新加載在未來:

your_mem_func.save_memo('yournewdata.json') 
+0

它看起來不錯。但如何使用這個類?對不起... – seguso 2013-05-09 14:21:24

+1

@seguso - 更新了使用情況。更多關於memoization:http://stackoverflow.com/questions/1988804/what-is-memoization-and-how-can-i-use-it-in-python – root 2013-05-09 14:31:15

+0

-1使用不需要的類時 – Merlin 2013-05-09 14:42:07

11

退房joblib.Memory。這是一個完全做到這一點的圖書館。

+1

只需三行代碼! :) – Andrew 2017-10-05 03:52:08

+1

哇,這是一個偉大的圖書館!我無法相信我沒有joblib這麼多年。這應該是IMO的正確答案。 – foobarbecue 2017-10-29 04:31:55

0

Artemis library有一個這個模塊。 (你需要pip install artemis-ml

你裝飾你的函數:

from artemis.fileman.disk_memoize import memoize_to_disk 

@memoize_to_disk 
def fcn(a, b, c = None): 
    results = ... 
    return results 

在內部,它通過這個哈希使哈希出的輸入參數和保存備忘文件。

0

假設你的數據JSON序列化,這個代碼應工作

import os, json 

def json_file(fname): 
    def decorator(function): 
     def wrapper(*args, **kwargs): 
      if os.path.isfile(fname): 
       with open(fname, 'r') as f: 
        ret = json.load(f) 
      else: 
       with open(fname, 'w') as f: 
        ret = function(*args, **kwargs) 
        json.dump(ret, f) 
      return ret 
     return wrapper 
    return decorator 

裝飾getHtmlOfUrl,然後簡單地調用它,如果它之前已經運行,你會得到你的緩存數據。

經過與Python 2.x和蟒3.x的

0

一個清潔溶液搭載Python的擱置模塊。優點是緩存實時更新,這是例外證明。

import shelve 
def shelve_it(file_name): 
    d = shelve.open(file_name) 

    def decorator(func): 
     def new_func(param): 
      if param not in d: 
       d[param] = func(param) 
      return d[param] 

     return new_func 

    return decorator 

@shelve_it('cache.shelve') 
def expensive_funcion(param): 
    pass 

這將有助於函數被計算一次。接下來的後續調用相同的參數將返回存儲的結果。