Python: argumento específico del decorador (no relacionado con la función envuelta)?
Pregunta
Estoy buscando construir un decorador de almacenamiento en caché que, dada una función, almacena en caché el resultado de la función en una ubicación especificada en la decoración. Algo como esto:
@cacheable('/path/to/cache/file')
def my_function(a, b, c):
return 'something'
El argumento para el decorador está completamente separado del argumento de la función que está envolviendo. He visto bastantes ejemplos, pero no estoy entendiendo cómo hacer esto: ¿es posible tener un argumento para el decorador que no esté relacionado y que no se pase a la función envuelta?
Solución
La idea es que su decorador es una función que devuelve un decorador.
PRIMERO Escribe tu decorador como si supieras que tu argumento es una variable global. Digamos algo como:
-
def decorator(f):
def decorated(*args,**kwargs):
cache = Cache(cachepath)
if cache.iscached(*args,**kwargs):
...
else:
res = f(*args,**kwargs)
cache.store((*args,**kwargs), res)
return res
return decorated
ENTONCES Escribe una función que tome cachepath como argumento y devuelva tu decorador.
-
def cache(filepath)
def decorator(f):
def decorated(*args,**kwargs):
cache = Cache(cachepath)
if cache.iscached(*args,**kwargs):
...
else:
res = f(*args,**kwargs)
cache.store((*args,**kwargs), res)
return res
return decorated
return decorator
Otros consejos
Sí lo es. Como sabes, un decorador es una función. Cuando se escribe en el formulario:
def mydecorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@mydecorator
def foo(a, b, c):
pass
el argumento pasado a mydecorator
es la función foo
en sí misma.
Cuando el decorador acepta un argumento, la llamada @mydecorator('/path/to')
realmente llamará a la función mydecorator con '/ path / to' primero. Luego se llamará al resultado de la llamada a mydecorator(path)
para recibir la función <=>. Estás definiendo efectivamente una función de envoltura dinámica.
En pocas palabras, necesita otra capa de funciones de decorador.
Aquí está este ejemplo un poco tonto:
def addint(val):
def decorator(func):
def wrapped(*args, **kwargs):
result = func(*args, **kwargs)
return result + val
return wrapped # returns the decorated function "add_together"
return decorator # returns the definition of the decorator "addint"
# specifically built to return an extra 5 to the sum
@addint(5)
def add_together(a, b):
return a + b
print add_together(1, 2)
# prints 8, not 3
La respuesta de Paul es buena, movería el objeto de caché para que no sea necesario construirlo todo el tiempo, y diseñaría su caché para que genere KeyError cuando falte el caché:
def cache(filepath): def decorator(f): f._cache = Cache(cachepath) def decorated(*args,**kwargs): try: key = (args, kwargs) res = f._cache.get(key) except KeyError: res = f(*args, **kwargs) f._cache.put(key, res) return res return decorated return decorator