memoize to disk-python-memoization persistente
(7)
¿Hay alguna forma de memorizar la salida de una función al disco?
Tengo una función
def getHtmlOfUrl(url):
... # expensive computation
y me gustaría hacer algo como:
def getHtmlMemoized(url) = memoizeToFile(getHtmlOfUrl, "file.dat")
y luego llame a getHtmlMemoized (url), para hacer el cálculo costoso solo una vez por cada url.
Algo como esto debería hacer:
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]
Uso básico:
your_mem_func = Memoize(your_func)
your_mem_func.load_memo(''yourdata.json'')
# do your stuff with your_mem_func
Si desea escribir su "caché" en un archivo después de usarlo, para volver a cargarlo en el futuro:
your_mem_func.save_memo(''yournewdata.json'')
Echa un vistazo a joblib.Memory
. Es una biblioteca para hacer exactamente eso.
La biblioteca de Artemisa tiene un módulo para esto. (tendrás que pip install artemis-ml
)
Tú decoras tu función:
from artemis.fileman.disk_memoize import memoize_to_disk
@memoize_to_disk
def fcn(a, b, c = None):
results = ...
return results
Internamente, hace un hash de los argumentos de entrada y guarda archivos memo por este hash.
Puedes usar el paquete cache_to_disk:
from cache_to_disk import cache_to_disk
@cache_to_disk(3)
def my_func(a, b, c, d=None):
results = ...
return results
Esto almacenará en caché los resultados durante 3 días, específicos de los argumentos a, b, c y d. Los resultados se almacenan en un archivo pickle en su máquina, y se seleccionan y devuelven la próxima vez que se llama a la función. Después de 3 días, el archivo pickle se elimina hasta que la función se vuelve a ejecutar. La función se volverá a ejecutar siempre que se llame a la función con nuevos argumentos. Más información aquí: https://github.com/sarenehan/cache_to_disk
Python ofrece una forma muy elegante de hacer esto: decoradores. Básicamente, un decorador es una función que envuelve otra función para proporcionar funcionalidad adicional sin cambiar el código fuente de la función. Tu decorador puede escribirse así:
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
Una vez que tengas eso, ''decora'' la función usando @ -syntax y estás listo.
@persist_to_file(''cache.dat'')
def html_of_url(url):
your function code...
Tenga en cuenta que este decorador está simplificado intencionalmente y puede que no funcione en todas las situaciones, por ejemplo, cuando la función de origen acepta o devuelve datos que no pueden ser serializados por json.
Más sobre decoradores: ¿Cómo hacer una cadena de decoradores funcionales?
Y aquí es cómo hacer que el decorador guarde el caché solo una vez, al momento de la salida:
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
Suponiendo que sus datos son serializables json, este código debería funcionar
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
decorate getHtmlOfUrl
y luego simplemente getHtmlOfUrl
, si se ejecutó anteriormente, obtendrá sus datos en caché.
Comprobado con python 2.xy python 3.x
Una solución más limpia impulsada por el módulo Shelve de Python. La ventaja es que la memoria caché se actualiza en tiempo real sin una sintaxis dict
conocida, también es una prueba de excepción (no hay necesidad de manejar KeyError
molesto).
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
Esto facilitará que la función se calcule solo una vez. Las siguientes llamadas subsiguientes para el mismo parámetro devolverán el resultado almacenado.