¿Existe una biblioteca de almacenamiento en caché de Python?


123

Estoy buscando una biblioteca de almacenamiento en caché de Python pero no puedo encontrar nada hasta ahora. Necesito una dictinterfaz sencilla en la que pueda configurar las claves y su caducidad y volver a almacenarlas en caché. Algo así como:

cache.get(myfunction, duration=300)

que me dará el elemento del caché si existe o llamará a la función y lo almacenará si no lo hace o ha expirado. ¿Alguien sabe algo como esto?


Creo que te falta itemen tu ejemplo.
SilentGhost

Sí, esto probablemente necesitaría una clave ... Y, 2.x.
Stavros Korokithakis

3
dentro del mismo proceso o compartido entre procesos? roscado o no?
Aaron Watters

1
Debería ser seguro para subprocesos, lo siento, debería haberlo mencionado. No necesito compartir entre procesos.
Stavros Korokithakis

6
Pruebe DiskCache : Apache2 con licencia, 100% de cobertura, seguro para subprocesos, seguro para procesos, múltiples políticas de desalojo y rápido (puntos de referencia) .
GrantJ

Respuestas:



72

Desde Python 3.2 puede usar el decorador @lru_cache de la biblioteca functools. Es un caché de último uso reciente, por lo que no hay tiempo de vencimiento para los elementos que contiene, pero como un truco rápido es muy útil.

from functools import lru_cache

@lru_cache(maxsize=256)
def f(x):
  return x*x

for x in range(20):
  print f(x)
for x in range(20):
  print f(x)

20
cachetools ofrece una buena implementación de estos y es compatible con python 2 y python 3.
vaab

1
big +1 para cachetools ... parece bastante bueno y tiene un par de algoritmos de almacenamiento en caché más :)
Jörn Hees

¡Esto nunca debería sugerirse! Mantente compatible.
PascalVKooten

1
@roboslone, dos años (menos 4 días ...) desde su comentario sobre no ser seguro para subprocesos, puede haber cambiado. Tengo cachetools 2.0.0 y veo en el código que usa un RLock. /usr/lib/python2.7/site-packages/cachetools/func.py
Motty

@Motty: La documentación de cachetools 4.0.0.0 dice lo siguiente: "Tenga en cuenta que todas estas clases no son seguras para subprocesos . El acceso a una caché compartida desde varios subprocesos debe sincronizarse correctamente, por ejemplo, utilizando uno de los decoradores de memoizing con un objeto de bloqueo adecuado "(negrita mía)
martineau

28

También puede echar un vistazo al decorador Memoize . Probablemente podrías conseguir que haga lo que quieras sin demasiadas modificaciones.


Eso es inteligente. Algunos cambios y el decorador podrían incluso caducar después de un tiempo establecido.
Ehtesh Choudhury

Definitivamente, podría escribir un límite basado en el espacio para la caché en el decorador. Eso sería útil si quisiera que una función, por ejemplo, genere la secuencia de fibonacci término por término. Desea almacenamiento en caché, pero solo necesita los dos últimos valores; guardarlos todos es simplemente ineficiente en cuanto al espacio.
reem el

14

Joblib https://joblib.readthedocs.io admite funciones de almacenamiento en caché en el patrón Memoize. Principalmente, la idea es almacenar en caché funciones computacionalmente costosas.

>>> from joblib import Memory
>>> mem = Memory(cachedir='/tmp/joblib')
>>> import numpy as np
>>> square = mem.cache(np.square)
>>> 
>>> a = np.vander(np.arange(3)).astype(np.float)
>>> b = square(a)                                   
________________________________________________________________________________
[Memory] Calling square...
square(array([[ 0.,  0.,  1.],
       [ 1.,  1.,  1.],
       [ 4.,  2.,  1.]]))
___________________________________________________________square - 0...s, 0.0min

>>> c = square(a)

También puedes hacer cosas sofisticadas como usar el decorador @ memory.cache en funciones. La documentación está aquí: https://joblib.readthedocs.io/en/latest/generated/joblib.Memory.html


2
Como nota al margen, joblib realmente brilla cuando se trabaja con arreglos NumPy grandes, ya que tiene métodos especiales para tratarlos específicamente.
alexbw


9

Creo que la API memcached de Python es la herramienta predominante, pero no la he usado yo mismo y no estoy seguro de si es compatible con las funciones que necesita.


3
Ese es el estándar de la industria, pero todo lo que quiero es un mecanismo de almacenamiento en memoria simple que pueda contener aproximadamente 100 claves, y Memcached es un poco exagerado. Sin embargo, gracias por la respuesta.
Stavros Korokithakis

7
import time

class CachedItem(object):
    def __init__(self, key, value, duration=60):
        self.key = key
        self.value = value
        self.duration = duration
        self.timeStamp = time.time()

    def __repr__(self):
        return '<CachedItem {%s:%s} expires at: %s>' % (self.key, self.value, time.time() + self.duration)

class CachedDict(dict):

    def get(self, key, fn, duration):
        if key not in self \
            or self[key].timeStamp + self[key].duration < time.time():
                print 'adding new value'
                o = fn(key)
                self[key] = CachedItem(key, o, duration)
        else:
            print 'loading from cache'

        return self[key].value



if __name__ == '__main__':

    fn = lambda key: 'value of %s  is None' % key

    ci = CachedItem('a', 12)
    print ci 
    cd = CachedDict()
    print cd.get('a', fn, 5)
    time.sleep(2)
    print cd.get('a', fn, 6)
    print cd.get('b', fn, 6)
    time.sleep(2)
    print cd.get('a', fn, 7)
    print cd.get('b', fn, 7)

5
Hice algo así, pero necesitas bloqueos para subprocesos múltiples y un parámetro de tamaño para evitar que crezca infinitamente. Entonces necesita alguna función para ordenar las claves por accesos para descartar las menos accedidas, etc, etc ...
Stavros Korokithakis

La línea de repetición es incorrecta (debería usar self.timeStamp). Además, es una implementación deficiente que hace cálculos matemáticos innecesariamente para cada get (). El tiempo de caducidad debe calcularse en CachedItem init.
ivo

1
De hecho, si solo está implementando el getmétodo, esta no debería ser una subclase dict, debería ser un objeto con un dict incrustado.
ivo

6

Puedes usar mi sencilla solución al problema. Es realmente sencillo, nada sofisticado:

class MemCache(dict):
    def __init__(self, fn):
        dict.__init__(self)
        self.__fn = fn

    def __getitem__(self, item):
        if item not in self:
            dict.__setitem__(self, item, self.__fn(item))
        return dict.__getitem__(self, item)

mc = MemCache(lambda x: x*x)

for x in xrange(10):
    print mc[x]

for x in xrange(10):
    print mc[x]

De hecho, carece de funcionalidad de caducidad, pero puede ampliarla fácilmente especificando una regla particular en MemCache c-tor.

El código de Hope se explica por sí mismo, pero si no, solo para mencionar, que a la caché se le está pasando una función de traducción como uno de sus parámetros de c-tor. Se usa a su vez para generar una salida en caché con respecto a la entrada.

Espero eso ayude


1
+1 por sugerir algo simple. Dependiendo del problema, podría ser simplemente la herramienta para el trabajo. PS No es necesario el elseen __getitem__:)
hiwaylon

¿Por qué no necesitaría hacerlo elseen el __getitem__? Ahí es donde puebla el dictado ...
Nils Ziehn

5

Prueba redis, es una de las soluciones más limpias y sencillas para que las aplicaciones compartan datos de forma atómica o si tienes alguna plataforma de servidor web. Es muy fácil de configurar, necesitará un cliente python redis http://pypi.python.org/pypi/redis


1
Debe mencionarse que está fuera de proceso, se debe acceder a través de TCP.
jeffry copps


2

Este proyecto tiene como objetivo proporcionar "Almacenamiento en caché para humanos" (aunque parece que es bastante desconocido)

Alguna información de la página del proyecto:

Instalación

caché de instalación de pip

Uso:

import pylibmc
from cache import Cache

backend = pylibmc.Client(["127.0.0.1"])

cache = Cache(backend)

@cache("mykey")
def some_expensive_method():
    sleep(10)
    return 42

# writes 42 to the cache
some_expensive_method()

# reads 42 from the cache
some_expensive_method()

# re-calculates and writes 42 to the cache
some_expensive_method.refresh()

# get the cached value or throw an error
# (unless default= was passed to @cache(...))
some_expensive_method.cached()


-5

keyring es la mejor biblioteca de almacenamiento en caché de Python. Puedes usar

keyring.set_password("service","jsonkey",json_res)

json_res= keyring.get_password("service","jsonkey")

json_res= keyring.core.delete_password("service","jsonkey")

Esa es una biblioteca de llaveros, no una biblioteca de almacenamiento en caché.
Stavros Korokithakis

@StavrosKorokithakis En realidad, implementé el almacenamiento en caché de claves a través del llavero
imp
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.