¿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?

Stavros Korokithakis
fuente
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)
Genma
fuente
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.

tgray
fuente
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

j13r
fuente
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
12

Nadie ha mencionado la estantería todavía. https://docs.python.org/2/library/shelve.html

No está almacenado en Memcached, pero parece mucho más simple y podría adaptarse a sus necesidades.

NuclearPeon
fuente
Escribí un contenedor seguro para subprocesos y multiprocesos para el módulo de estantería estándar (incluida una función auxiliar para almacenar en caché las solicitudes http) en caso de que sea útil para cualquiera: github.com/cristoper/shelfcache
cristoper
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.

David Berger
fuente
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)
Tzury Bar Yochay
fuente
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

Jakub Koszuliński
fuente
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

Harry
fuente
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()
Vano
fuente
-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")
diablillo
fuente
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