2012-04-16 19 views
64

Ho un decoratore come qui di seguitoCome passare argomenti extra a python decorator?

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

voglio migliorare questo decoratore di accettare un altro argomento, come di seguito

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

Ma questo codice dà l'errore,

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

Perché la funzione non passa automaticamente? Come faccio a passare esplicitamente la funzione alla funzione decoratore?

+2

Balki: si prega di evitare l'uso di booleano come argomento, non è un approccio gd e ridurre del codice readliability –

+7

@KitHo - è un flag booleano, in modo da utilizzare un valore booleano è l'approccio giusto. – AKX

+0

@KitHo - che cos'è "gd"? È buono"? –

risposta

107

Dal momento che si sta chiamando il decoratore come una funzione, si ha la necessità di tornare un'altra funzione, che è il decoratore attuale:

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 funzione esterna sarà dato alcun argomento che si passa in modo esplicito, e dovrebbe restituire il interno funzione. La funzione interna verrà passata alla funzione per decorare e restituire la funzione modificata.

In genere si desidera che il decoratore modifichi il comportamento della funzione avvolgendolo in una funzione wrapper. Ecco un esempio che aggiunge opzionalmente la registrazione quando viene chiamata la funzione di:

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 

I functools.wraps copia chiamata cose come il nome e docstring alla funzione wrapper, per renderlo più simile alla funzione originaria. utilizzo

Esempio:

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

E 'consigliabile usare ['functools.wraps'] (http://docs.python.org/library/functools.html#functools.wraps) - mantiene il nome originale, docstring, ecc. Della funzione incartata. – AKX

+0

@AKX: Grazie, ho aggiunto questo al secondo esempio. – interjay

+1

Quindi fondamentalmente decoratore prende sempre solo un argomento che è la funzione. Ma il decoratore può essere un valore di ritorno di una funzione che potrebbe assumere argomenti. È corretto? – balki

34

solo per fornire un punto di vista diverso: la sintassi

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

è equivalente a

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

In particolare, expr può essere qualcosa che ti piace, fintanto che valuta un callable. Nello specifico , expr può essere una fabbrica di decorazioni: gli dai alcuni parametri e ti dà un decoratore. Quindi forse un modo migliore per capire la vostra situazione è la

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

che possono poi essere ridotto a

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

Naturalmente, dal momento che sembra come decorator_factory è un decoratore, le persone tendono a nominarlo per riflettere questo. Che può essere fonte di confusione quando si tenta di seguire i livelli di riferimento indiretto.

14

Voglio solo aggiungere qualche trucco utile che permetterà di rendere facoltativi gli argomenti del decoratore. Permette anche di riutilizzare decorator e diminuire l'annidamento

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

Solo un altro modo di fare decoratori. Trovo in questo modo il modo più semplice per avvolgere la mia testa.

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() 
Problemi correlati