2013-03-25 17 views
5

Perché questo decoratore con un parametro non funziona?Passare un parametro al decoratore in python

def decAny(f0): 
    def wrapper(s0): 
     return "<%s> %s </%s>" % (any, f0(), any) 
    return wrapper 

@decAny('xxx') 
def test2(): 
    return 'test1XML' 

print(test2()) 

mi dà sempre un errore che dice "str non è callable" si sta cercando di eseguire la stringa di ritorno all'interno del involucro() invece di elaborarlo e restituire la stringa risultato

+1

Pensare in questo modo: Prima ancora di _get_ per decorare 'test2', stai chiamando' decAny ('xxx') '. Ma 'decAny' prende una funzione,' f0', non una stringa. Quindi chiaramente ad un certo punto, che 'f0()' proverà a chiamare ''xxx''. – abarnert

+0

Ok, ma come in un decoratore senza parametri perché il compilatore non assume che il primo parametro sia la funzione client ... – ZEE

+1

Non è una questione di parametri. Se hai '@ decAny', è solo usando' decAny' stesso come decoratore. Ma se hai '@decAny()', si chiama 'decAny' prima ancora di arrivare alla decorazione, proprio come' @decAny ('xxx') 'è. (È proprio come quando passi le funzioni come valori, li memorizzi in variabili, ecc., Anziché chiamarli.) – abarnert

risposta

13

decoratori sono funzioni che funzioni di ritorno. Quando "passando un parametro al decoratore", ciò che stai facendo in realtà è chiamare una funzione che restituisce un decoratore. Quindi decAny() dovrebbe essere una funzione che restituisce una funzione che restituisce una funzione.

Sarebbe simile a questa:

import functools 

def decAny(tag): 
    def dec(f0): 
     @functools.wraps(f0) 
     def wrapper(*args, **kwargs): 
      return "<%s> %s </%s>" % (tag, f0(*args, **kwargs), tag) 
     return wrapper 
    return dec 

@decAny('xxx') 
def test2(): 
    return 'test1XML' 

Esempio:

>>> print(test2()) 
<xxx> test1XML </xxx> 

Nota che, oltre a risolvere il problema specifico che si stavano colpendo Ho anche migliorato il codice un po 'con l'aggiunta di *args e **kwargs come argomenti per la funzione avvolta e passandoli sulla chiamata f0 all'interno del decoratore. In questo modo è possibile decorare una funzione che accetta un numero qualsiasi di argomenti posizionali o denominati e funzionerà comunque correttamente.

È possibile leggere su functools.wraps() qui:
http://docs.python.org/2/library/functools.html#functools.wraps

+0

[PEP 318] (http://www.python.org/dev/peps/pep-0318/) ha esempi che mostrano questo modello (imposizione di attributi di tipo o interfacce, 'sincronizzare', ecc.). – abarnert

+1

Se vuoi aggiungere il '* args, ** kwargs' per migliorare il suo codice (senza spiegare perché), probabilmente vuoi anche aggiungere' functools.wraps'. – abarnert

+0

@abarnert Grazie per il suggerimento, aggiunto 'functools.wraps' e qualche spiegazione aggiuntiva. –

1

V'è un buon esempio da "Mark Lutz - Learning Python" libro:

def timer(label=''): 
    def decorator(func): 
     def onCall(*args): # Multilevel state retention: 
      ...    # args passed to function 
      func(*args)  # func retained in enclosing scope 
      print(label, ... # label retained in enclosing scope 
     return onCall 
    return decorator   # Returns the actual decorator 

@timer('==>')    # Like listcomp = timer('==>')(listcomp) 
def listcomp(N): ...   # listcomp is rebound to new onCall 

listcomp(...)    # Really calls onCall 
Problemi correlati