Python: argumento específico decorador (sem relação com a função envolto)?
Pergunta
Eu estou olhando para construir um decorador de cache que dada uma função armazena em cache o resultado da função para um local especificado na decoração. Algo parecido com isto:
@cacheable('/path/to/cache/file')
def my_function(a, b, c):
return 'something'
O argumento para o decorador é completamente separado do argumento para a função que de embrulho. Eu olhei para algumas exemplos, mas eu não estou bastante recebendo como fazer isso - é possível ter um argumento para o decorador que está relacionado com e não passado para a função envolto
?Solução
A idéia é que o decorador é uma função retornando um decorador.
PRIMEIRO Escreva seu decorador como se você soubesse o seu argumento era uma variável global. Vamos dizer 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
THEN Escrever uma função que leva CachePath como arg e devolver o 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
Outras dicas
Sim, é. Como você sabe, um decorador é uma função. Quando escrito na forma:
def mydecorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@mydecorator
def foo(a, b, c):
pass
o argumento passado para mydecorator
é a função em si foo
.
Quando o decorador aceita um argumento, o @mydecorator('/path/to')
chamada está realmente indo para chamar a função mydecorator com '/ path / to' em primeiro lugar. Em seguida, o resultado da chamada para mydecorator(path)
será chamado para receber a função foo
. Você está definindo efetivamente uma função wrapper dinâmica.
Em poucas palavras, você precisa de uma outra camada de funções decorador.
Aqui está este exemplo ligeiramente bobo:
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
A resposta de Paulo é bom, eu iria mover o objeto de cache para que ele não precisa ser construído a cada vez, e projetar seu cache para que ele levanta KeyError quando há um erro de cache:
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