2010-01-19 16 views
8

Perché i seguenti due script non sono equivalenti?Perché i decoratori Python non possono essere concatenati tra le definizioni?

(Tratto da un'altra domanda: Understanding Python Decorators)

def makebold(fn): 
    def wrapped(): 
     return "<b>" + fn() + "</b>" 
    return wrapped 

def makeitalic(fn): 
    def wrapped(): 
     return "<i>" + fn() + "</i>" 
    return wrapped 

@makebold 
@makeitalic 
def hello(): 
    return "hello world" 

print hello() ## returns <b><i>hello world</i></b> 

e con un decoratore decorato:

def makebold(fn): 
    def wrapped(): 
     return "<b>" + fn() + "</b>" 
    return wrapped 

@makebold 
def makeitalic(fn): 
    def wrapped(): 
     return "<i>" + fn() + "</i>" 
    return wrapped 

@makeitalic 
def hello(): 
    return "hello world" 

print hello() ## TypeError: wrapped() takes no arguments (1 given) 

Perché voglio sapere? Ho scritto un decoratore retry per catturare le eccezioni MySQLdb - se l'eccezione è transitoria (ad es. Timeout) richiamerà la funzione dopo aver dormito un po '.

Ho anche un decoratore modifies_db che si occupa di alcune operazioni di pulizia relative alla cache. modifies_db è decorato con retry, quindi ho presupposto che tutte le funzioni decorate con modifies_db riprovassero implicitamente. Dove ho sbagliato?

+0

Buona domanda. Mi sono imbattuto nello stesso scenario pochi mesi fa mentre facevo il mio decoratore di nuovi tentativi. Ci sono voluti un conteggio dei tentativi, quindi il problema si presentava un po 'diverso dal tuo, ma aveva le stesse soluzioni viste di seguito. –

risposta

9

Il problema con il secondo esempio è che

@makebold 
def makeitalic(fn): 
    def wrapped(): 
     return "<i>" + fn() + "</i>" 
    return wrapped 

sta cercando di decorare makeitalic, il decoratore, e non wrapped, la funzione che restituisce.

Si può fare quello che penso che si intende con qualcosa di simile:

def makeitalic(fn): 
    @makebold 
    def wrapped(): 
     return "<i>" + fn() + "</i>" 
    return wrapped 

Qui makeitalic utilizza makebold per decorare wrapped.

+2

+1 Questo indica esattamente il problema e come invece deve essere fatto. Fondamentalmente il problema viene dal pensare che i decoratori facciano la composizione funzionale, ma non lo fanno. –

+0

Sì, è esattamente quello che stavo cercando di fare, e sì ho frainteso che i decoratori == composizione funzionale – RobM

1

Il motivo è dovuto al fatto che wrapped() all'interno di makebold non accetta alcun argomento.

Quando si utilizza il decoratore in modo che possa causare alcuni problemi, pubblicherò un esempio su come ottenere ciò che vuoi, dammi solo un momento.

Ecco un esempio funzionante di ciò che ti serve.

def makebold(rewrap=False): 
    if rewrap: 
     def inner(decorator): 
      def rewrapper(func): 
       def wrapped(*args, **kwargs): 
        return "<b>%s</b>" % decorator(func)(*args,**kwargs) 
       return wrapped 
      return rewrapper 
     return inner 

    else: 
     def inner(func): 
      def wrapped(*args, **kwargs): 
       return "<b>%s</b>" % func(*args, **kwargs)  
      return wrapped 
     return inner 

@makebold(rewrap=True) 
def makeitalic(fn): 
    def wrapped(*args, **kwargs): 
     return "<i>%s</i>" % fn(*args, **kwargs) 
    return wrapped 

@makeitalic 
def hello(): 
    return "hello world" 

@makebold() 
def hello2(): 
    return "Bob Dole"  

if __name__ == "__main__": 
    print hello() 
    print hello2() 

makebold è un pò brutto, ma vi mostra come scrivere un decoratore che può opzionalmente avvolgere un'altra decoratore.

Ecco l'output dello script di cui sopra:

<b><i>hello world</i></b> 
<b>Bob Dole</b> 

Nota che makebold è l'unico decoratore ricorsiva. Notare anche la sottile differenza di utilizzo: @makebold() rispetto a @makeitalic.

+0

-1 ... troppo complesso, e "makeitalic" è un brutto nome per qualcosa che fa grassetto + corsivo.Vedi [questa risposta] (http://stackoverflow.com/a/739665/850830) che fa la stessa cosa in poche righe di codice. – Ryan

0

Il problema sta sostituendo "makeitalic" (che prende un argomento) con il-funzione "avvolto" in "makebold" che prende zero argomenti.

Usa *args, **kwargs di trasmettere argomenti più in basso la catena:

def wrapped(*args, **kwargs): 
    return "<b>" + fn(*args, **kwargs) + "</b>" 
+0

il problema è un po 'più grave di quello in realtà. avvolgere un altro decoratore ha qualche problema in più. –

Problemi correlati