Obviamente, una búsqueda rápida produce un millón de implementaciones y sabores del decorador de memorias en Python. Sin embargo, estoy interesado en un sabor que no he podido encontrar. Me gustaría tenerlo tal que la memoria caché de valores almacenados pueda tener una capacidad fija. Cuando se agregan nuevos elementos, si se alcanza la capacidad, se elimina el valor más antiguo y se reemplaza con el valor más nuevo.¿Cómo creo un decorador de memorando delimitado en Python?
Mi preocupación es que, si uso la memorización para almacenar muchos elementos, el programa se bloqueará debido a la falta de memoria. (No sé cuán bien ubicada esta preocupación puede ser en la práctica.) Si la memoria caché era de un tamaño fijo, entonces un error de memoria no sería un problema. Y muchos problemas en los que trabajo cambian a medida que el programa se ejecuta, de modo que los valores iniciales en caché se verían muy diferentes de los valores almacenados en caché posteriores (y sería mucho menos probable que se repitan más adelante). Es por eso que me gustaría que las cosas más antiguas sean reemplazadas por las más nuevas.
Encontré la clase OrderedDict
y un ejemplo que muestra cómo subclase para especificar un tamaño máximo. Me gustaría usar eso como mi caché, en lugar de un dict
normal. El problema es que necesito que el decorador de memoize tome un parámetro llamado maxlen
cuyo valor predeterminado es None
. Si es None
, entonces el caché no tiene límites y funciona normalmente. Cualquier otro valor se usa como el tamaño de la memoria caché.
quiero que funcione como la siguiente:
@memoize
def some_function(spam, eggs):
# This would use the boundless cache.
pass
y
@memoize(200) # or @memoize(maxlen=200)
def some_function(spam, eggs):
# This would use the bounded cache of size 200.
pass
A continuación se muestra el código que tengo hasta ahora, pero no veo cómo pasar el parámetro en el decorador mientras lo hace funcionar tanto "desnudo" como con un parámetro.
import collections
import functools
class BoundedOrderedDict(collections.OrderedDict):
def __init__(self, *args, **kwds):
self.maxlen = kwds.pop("maxlen", None)
collections.OrderedDict.__init__(self, *args, **kwds)
self._checklen()
def __setitem__(self, key, value):
collections.OrderedDict.__setitem__(self, key, value)
self._checklen()
def _checklen(self):
if self.maxlen is not None:
while len(self) > self.maxlen:
self.popitem(last=False)
def memoize(function):
cache = BoundedOrderedDict() # I want this to take maxlen as an argument
@functools.wraps(function)
def memo_target(*args):
lookup_value = args
if lookup_value not in cache:
cache[lookup_value] = function(*args)
return cache[lookup_value]
return memo_target
@memoize
def fib(n):
if n < 2: return 1
return fib(n-1) + fib(n-2)
if __name__ == '__main__':
x = fib(50)
print(x)
Editar: El uso de la sugerencia de Ben, que crea el siguiente decorador, que creo que funciona de la manera que imaginaba. Es importante para mí poder usar estas funciones decoradas con multiprocessing
, y eso ha sido un problema en el pasado. Pero una prueba rápida de este código parecía funcionar correctamente, incluso cuando se trabaja en un conjunto de hilos.
def memoize(func=None, maxlen=None):
if func:
cache = BoundedOrderedDict(maxlen=maxlen)
@functools.wraps(func)
def memo_target(*args):
lookup_value = args
if lookup_value not in cache:
cache[lookup_value] = func(*args)
return cache[lookup_value]
return memo_target
else:
def memoize_factory(func):
return memoize(func, maxlen=maxlen)
return memoize_factory
No del todo. En la pregunta pedí algo que podría funcionar de manera equivalente con y sin el parámetro. No creo que este pueda hacer eso. – agarrett
Lo siento, he editado mi respuesta. – srgerg