Python: argumento específico decorador (sem relação com a função envolto)?

StackOverflow https://stackoverflow.com/questions/660727

  •  20-08-2019
  •  | 
  •  

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

?
Foi útil?

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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top