2012-04-16 20 views
64

tengo un decorador, como a continuación¿Cómo pasar argumentos adicionales a python decorator?

def myDecorator(test_func): 
    return callSomeWrapper(test_func) 
def callSomeWrapper(test_func): 
    return test_func 
@myDecorator 
def someFunc(): 
    print 'hello' 

quiero mejorar este decorador para aceptar otro argumento, como a continuación

def myDecorator(test_func,logIt): 
    if logIt: 
     print "Calling Function: " + test_func.__name__ 
    return callSomeWrapper(test_func) 
@myDecorator(False) 
def someFunc(): 
    print 'Hello' 

Pero este código da el error,

TypeError: myDecorator() takes exactly 2 arguments (1 given) 

¿Por qué es la función no pasó automáticamente? ¿Cómo transfiero explícitamente la función a la función de decorador?

+2

balki: por favor, evite usar el booleano como su argumento, no es un enfoque gd y reducir de código readliability –

+7

@KitHo - es un indicador booleano, así que usar un valor booleano es el enfoque correcto. – AKX

+0

@KitHo - ¿Qué es "gd"? Esta bien"? –

Respuesta

107

Desde que está llamando el decorador como una función, tiene que volver otra función que es el decorador real:

def my_decorator(param): 
    def actual_decorator(func): 
     print("Decorating function {}, with parameter {}".format(func.__name__, param)) 
     return function_wrapper(func) # assume we defined a wrapper somewhere 
    return actual_decorator 

La función externa se dará ningún argumentos que pasa de forma explícita, y debe devolver el interior función. La función interna pasará la función para decorar y devolver la función modificada.

Normalmente desea que el decorador cambie el comportamiento de la función envolviéndolo en una función de envoltura. He aquí un ejemplo que añade opcionalmente registro cuando la función se llama:

def log_decorator(log_enabled): 
    def actual_decorator(func): 
     @functools.wraps(func) 
     def wrapper(*args, **kwargs): 
      if log_enabled: 
       print("Calling Function: " + func.__name__) 
      return func(*args, **kwargs) 
     return wrapper 
    return actual_decorator 

Los functools.wraps copias de llamadas cosas como el nombre y la cadena de documentación de la función de contenedor, para que sea más similar a la función original. el uso

Ejemplo:

>>> @log_decorator(True) 
... def f(x): 
...  return x+1 
... 
>>> f(4) 
Calling Function: f 
5 
+10

Y usar ['functools.wraps'] (http://docs.python.org/library/functools.html#functools.wraps) es aconsejable, ya que conserva el nombre original, la cadena de documentos, etc. de la función envuelta. – AKX

+0

@AKX: Gracias, agregué esto al segundo ejemplo. – interjay

+1

Así que básicamente el decorador siempre toma solo un argumento que es la función. Pero el decorador puede ser un valor de retorno de una función que podría tomar argumentos. ¿Es esto correcto? – balki

34

sólo para proporcionar un punto de vista diferente: la sintaxis

@expr 
def func(...): #stuff 

es equivalente a

def func(...): #stuff 
func = expr(func) 

En particular, expr puede ser cualquier cosa que te gusta, siempre que se evalúe como un invocable. En en particular en particular, expr puede ser una fábrica de decoradores: le da algunos parámetros y le da un decorador. Así que tal vez una mejor manera de entender su situación es tan

dec = decorator_factory(*args) 
@dec 
def func(...): 

que luego puede ser acortado a

@decorator_factory(*args) 
def func(...): 

Por supuesto, ya que ve como decorator_factory es un decorador, la gente tiende a nombrarlo para reflejar eso. Lo cual puede ser confuso cuando intentas seguir los niveles de indirección.

14

Solo quiero agregar algún truco útil que permita hacer que los argumentos del decorador sean opcionales. También permitirá reutilizar el decorador y disminuir la anidación

import functools 

def myDecorator(test_func=None,logIt=None): 
    if not test_func: 
     return functools.partial(myDecorator, logIt=logIt) 
    @functools.wraps(test_func) 
    def f(*args, **kwargs): 
     if logIt==1: 
      print 'Logging level 1 for {}'.format(test_func.__name__) 
     if logIt==2: 
      print 'Logging level 2 for {}'.format(test_func.__name__) 
     return test_func(*args, **kwargs) 
    return f 

#new decorator 
myDecorator_2 = myDecorator(logIt=2) 

@myDecorator(logIt=2) 
def pow2(i): 
    return i**2 

@myDecorator 
def pow3(i): 
    return i**3 

@myDecorator_2 
def pow4(i): 
    return i**4 

print pow2(2) 
print pow3(2) 
print pow4(2) 
1

Simplemente otra forma de hacer decoradores. Encuentro de esta manera el más fácil de entender.

import functools 

class NiceDecorator: 
    def __init__(self, param_foo='a', param_bar='b'): 
     self.param_foo = param_foo 
     self.param_bar = param_bar 

    def __call__(self, func): 
     @functools.wraps(func) 
     def my_logic(*args, **kwargs): 
      # whatever logic your decorator is supposed to implement goes in here 
      print('pre action baz') 
      print(self.param_bar) 
      # including the call to the decorated function (if you want to do that) 
      result = func(*args, **kwargs) 
      print('post action beep') 
      return result 

     return my_logic 

# usage example from here on 
@NiceDecorator(param_bar='baaar') 
def example(): 
    print('example yay') 


example() 
Cuestiones relacionadas