¿Hay un decorador para simplemente almacenar en caché los valores de retorno de la función?

Considera lo siguiente:

@property def name(self): if not hasattr(self, '_name'): # expensive calculation self._name = 1 + 1 return self._name 

Soy nuevo, pero creo que el almacenamiento en caché podría ser factorizado en un decorador. Solo que no encontré uno igual;)

PD: el cálculo real no depende de valores mutables.

A partir de Python 3.2 hay un decorador incorporado:

@functools.lru_cache(maxsize=100, typed=False)

Decorador para envolver una función con un memoizing ejecutable que ahorra hasta el máximo las llamadas más recientes. Puede ahorrar tiempo cuando se llama periódicamente a una función costosa o enlazada de E / S con los mismos argumentos.

Ejemplo de un caché LRU para calcular los números de Fibonacci :

 @lru_cache(maxsize=None) def fib(n): if n < 2: return n return fib(n-1) + fib(n-2) >>> print([fib(n) for n in range(16)]) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610] >>> print(fib.cache_info()) CacheInfo(hits=28, misses=16, maxsize=None, currsize=16) 

Si está atascado con Python 2.x, aquí hay una lista de otras bibliotecas de memorización compatibles:

  • functools32 | PyPI | Código fuente
  • repoze.lru | PyPI | Código fuente
  • pylru | PyPI | Código fuente
  • backports.functools_lru_cache | PyPI | Código fuente

Parece que no está solicitando un decorador de memorización de propósito general (es decir, no está interesado en el caso general en el que desea almacenar en caché los valores de retorno para diferentes valores de argumento). Es decir, te gustaría tener esto:

 x = obj.name # expensive y = obj.name # cheap 

mientras que un decorador de memorización de propósito general le daría esto:

 x = obj.name() # expensive y = obj.name() # cheap 

Sostengo que la syntax de llamada de método es mejor estilo, porque sugiere la posibilidad de cálculos costosos mientras que la syntax de propiedad sugiere una búsqueda rápida.

[Actualización: el decorador de memoización basado en clase al que he vinculado y citado aquí anteriormente no funciona con los métodos. Lo he reemplazado con una función de decorador.] Si está dispuesto a usar un decorador de memorización de propósito general, aquí hay uno simple:

 def memoize(function): memo = {} def wrapper(*args): if args in memo: return memo[args] else: rv = function(*args) memo[args] = rv return rv return wrapper 

Ejemplo de uso:

 @memoize def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) 

Aquí se puede encontrar otro decorador de memorias con un límite en el tamaño del caché.

 class memorize(dict): def __init__(self, func): self.func = func def __call__(self, *args): return self[args] def __missing__(self, key): result = self[key] = self.func(*key) return result 

Usos de muestra:

 >>> @memorize ... def foo(a, b): ... return a * b >>> foo(2, 4) 8 >>> foo {(2, 4): 8} >>> foo('hi', 3) 'hihihi' >>> foo {(2, 4): 8, ('hi', 3): 'hihihi'} 

Werkzeug tiene un decorador cached_property ( docs , source )

Codifiqué esta clase de decorador simple para almacenar en caché las respuestas de la función. Lo encuentro MUY útil para mis proyectos:

 from datetime import datetime, timedelta class cached(object): def __init__(self, *args, **kwargs): self.cached_function_responses = {} self.default_max_age = kwargs.get("default_cache_max_age", timedelta(seconds=0)) def __call__(self, func): def inner(*args, **kwargs): max_age = kwargs.get('max_age', self.default_max_age) if not max_age or func not in self.cached_function_responses or (datetime.now() - self.cached_function_responses[func]['fetch_time'] > max_age): if 'max_age' in kwargs: del kwargs['max_age'] res = func(*args, **kwargs) self.cached_function_responses[func] = {'data': res, 'fetch_time': datetime.now()} return self.cached_function_responses[func]['data'] return inner 

El uso es sencillo:

 import time @cached def myfunc(a): print "in func" return (a, datetime.now()) @cached(default_max_age = timedelta(seconds=6)) def cacheable_test(a): print "in cacheable test: " return (a, datetime.now()) print cacheable_test(1,max_age=timedelta(seconds=5)) print cacheable_test(2,max_age=timedelta(seconds=5)) time.sleep(7) print cacheable_test(3,max_age=timedelta(seconds=5)) 

DESCARGO DE RESPONSABILIDAD: Soy el autor de kids.cache .

Debes revisar kids.cache , proporciona un decorador @cache que funciona en python 2 y python 3. Sin dependencias, ~ 100 líneas de código. Es muy sencillo de usar, por ejemplo, con su código en mente, podría usarlo así:

 pip install kids.cache 

Entonces

 from kids.cache import cache ... class MyClass(object): ... @cache # <-- That's all you need to do @property def name(self): return 1 + 1 # supposedly expensive calculation 

O puedes poner el decorador @cache después de la @property (el mismo resultado).

El uso de caché en una propiedad se denomina evaluación perezosa , kids.cache puede hacer mucho más (funciona en función con cualquier argumento, propiedad, cualquier tipo de método e incluso clases ...). Para usuarios avanzados, kids.cache admite cachetools que proporciona almacenes de caché de lujo a python 2 y python 3 (LRU, LFU, TTL, RR caché).

NOTA IMPORTANTE : el almacén de caché predeterminado de kids.cache es un dict estándar, que no se recomienda para progtwigs de ejecución prolongada con consultas siempre diferentes, ya que conduciría a un almacén de almacenamiento en caché cada vez mayor. Para este uso, puede conectar otras tiendas de caché usando por ejemplo ( @cache(use=cachetools.LRUCache(maxsize=2)) para decorar su función / propiedad / clase / método ...)

Ah, solo necesitaba encontrar el nombre correcto para esto: ” Evaluación de propiedades perezosa “.

También hago esto mucho; quizás use esa receta en mi código alguna vez.

Hay otro ejemplo más de un decorador memoize en Python Wiki:

http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize

Ese ejemplo es un poco inteligente, porque no almacenará en caché los resultados si los parámetros son mutables. (Revisa ese código, es muy simple e interesante!)

Si está utilizando Django Framework, tiene esa propiedad para almacenar en caché una vista o respuesta de las API usando @cache_page(time) y también puede haber otras opciones.

Ejemplo:

 @cache_page(60 * 15, cache="special_cache") def my_view(request): ... 

Más detalles se pueden encontrar aquí .

Junto con el ejemplo de Memoize , encontré los siguientes paquetes de python:

  • cachepy Permite configurar ttl y / o el número de llamadas para funciones almacenadas en caché; Además, uno puede usar caché cifrado basado en archivos …
  • percache

Hay fastcache , que es “Implementación en C de Python 3 functools.lru_cache. Proporciona una aceleración de 10-30x sobre la biblioteca estándar”.

Igual que la respuesta elegida , solo diferente importación:

 from fastcache import lru_cache @lru_cache(maxsize=128, typed=False) def f(a, b): pass 

Además, viene instalado en Anaconda , a diferencia de las funciones que deben instalarse .

Implementé algo como esto, utilizando pickle para la persistencia y usando sha1 para IDs cortos casi seguros. Básicamente, el caché eliminó el código de la función y el historial de argumentos para obtener un sha1 y luego buscó un archivo con ese sha1 en el nombre. Si existió, lo abrió y devolvió el resultado; de lo contrario, llama a la función y guarda el resultado (opcionalmente, solo se guarda si se tarda un cierto tiempo en procesar).

Dicho esto, juraría que encontré un módulo existente que hizo esto y me encuentro aquí tratando de encontrar ese módulo … Lo más cercano que puedo encontrar es esto, que parece correcto: http: //chase-seibert.github. io / blog / 2011/11/23 / pythondjango-disk-based-caching-decorator.html

El único problema que veo con esto es que no funcionaría bien para entradas grandes, ya que contiene hash (arg), que no es exclusivo de matrices gigantes.

Sería bueno si hubiera un protocolo unique_hash () en el que una clase devolviera un hash seguro de su contenido. Básicamente, lo implementé manualmente para los tipos que me importaban.

@lru_cache no es perfecto con los valores de función predeterminados

mi decorador mem

 import inspect def get_default_args(f): signature = inspect.signature(f) return { k: v.default for k, v in signature.parameters.items() if v.default is not inspect.Parameter.empty } def full_kwargs(f, kwargs): res = dict(get_default_args(f)) res.update(kwargs) return res def mem(func): cache = dict() def wrapper(*args, **kwargs): kwargs = full_kwargs(func, kwargs) key = list(args) key.extend(kwargs.values()) key = hash(tuple(key)) if key in cache: return cache[key] else: res = func(*args, **kwargs) cache[key] = res return res return wrapper 

y código para la prueba:

 from time import sleep @mem def count(a, *x, z=10): sleep(2) x = list(x) x.append(z) x.append(a) return sum(x) def main(): print(count(1,2,3,4,5)) print(count(1,2,3,4,5)) print(count(1,2,3,4,5, z=6)) print(count(1,2,3,4,5, z=6)) print(count(1)) print(count(1, z=10)) if __name__ == '__main__': main() 

resultado – solo 3 veces con el sueño

pero con @lru_cache será 4 veces, porque esto:

 print(count(1)) print(count(1, z=10)) 

se calculará dos veces (mal funcionamiento con valores predeterminados)

Pruebe joblib http://pythonhosted.org/joblib/memory.html

 from joblib import Memory memory = Memory(cachedir=cachedir, verbose=0) @memory.cache def f(x): print('Running f(%s)' % x) return x 

Si está utilizando Django y desea almacenar en caché las vistas, consulte la respuesta de Nikhil Kumar .

Pero si desea almacenar en caché los resultados de CUALQUIER función, puede usar django-cache-utils .

Reutiliza los cachés de Django y proporciona un decorador de cached fácil de usar:

 from cache_utils.decorators import cached @cached(60) def foo(x, y=0): print 'foo is called' return x+y