2011-10-09 19 views
10

Sto scrivendo un decoratore di funzioni che applicherà una conversione al primo argomento della funzione. Funziona bene se decoro solo una volta le mie funzioni, ma se le decoro due volte ho un errore. Di seguito è riportato un codice che dimostra il problema, è una versione semplificata del codice su cui sto lavorando. Ho escluso il codice che esegue la conversione in modo da non distrarre dal problemaDecoratori di funzioni annidate che operano su argomenti in python

from inspect import getargspec 
from functools import wraps 

def dec(id): 
    def _dec(fn): 
     @wraps(fn) 
     def __dec(*args, **kwargs): 
      if len(args): 
       return fn(args[0], *args[1:], **kwargs) 
      else: 
       first_arg = getargspec(fn).args[0] 
       new_kwargs = kwargs.copy() 
       del new_kwargs[first_arg] 
       return fn(kwargs[first_arg], **new_kwargs) 
     return __dec 
    return _dec 

@dec(1) 
def functionWithOneDecorator(a, b, c): 
    print "functionWithOneDecorator(a = %s, b = %s, c = %s)" % (a, b, c) 

@dec(1) 
@dec(2) 
def functionWithTwoDecorators(a, b, c): 
    print "functionWithTwoDecorators(a = %s, b = %s, c = %s)" % (a, b, c) 

functionWithOneDecorator(1, 2, 3) 
functionWithOneDecorator(1, b=2, c=3) 
functionWithOneDecorator(a=1, b=2, c=3) 
functionWithOneDecorator(c=3, b=2, a=1) 

functionWithTwoDecorators(1, 2, 3) 
functionWithTwoDecorators(1, b=2, c=3) 
functionWithTwoDecorators(a=1, b=2, c=3) 
functionWithTwoDecorators(c=3, b=2, a=1) 

Quando si esegue il codice precedente ottengo il seguente output:

functionWithOneDecorator(a = 1, b = 2, c = 3) 
functionWithOneDecorator(a = 1, b = 2, c = 3) 
functionWithOneDecorator(a = 1, b = 2, c = 3) 
functionWithOneDecorator(a = 1, b = 2, c = 3) 
functionWithTwoDecorators(a = 1, b = 2, c = 3) 
functionWithTwoDecorators(a = 1, b = 2, c = 3) 
IndexError: list index out of range 

Questo perché quando il secondo decoratore controlla la funzione che sta decorando per trovare i nomi degli argomenti e fallisce perché decorare un decoratore e richiede solo * args e ** kwargs.

Mi viene in mente un modo per aggirare il problema che funzionerebbe nel codice sopra, ma si interromperebbe comunque se una funzione fosse decorata con il mio decoratore e un'altra da una terza parte. C'è un modo generale per risolvere questo problema? o c'è un modo migliore per ottenere lo stesso risultato?

Aggiornamento: Grazie a @Hernan per aver indicato lo decorator module. Risolve esattamente questo problema. Ora il mio codice è simile al seguente:

from decorator import decorator 

def dec(id): 
    @decorator 
    def _dec(fn, *args, **kwargs): 
     return fn(args[0], *args[1:], **kwargs) 
    return _dec 

@dec(1) 
def functionWithOneDecorator(a, b, c): 
    print "functionWithOneDecorator(a = %s, b = %s, c = %s)" % (a, b, c) 

@dec(1) 
@dec(2) 
def functionWithTwoDecorators(a, b, c): 
    print "functionWithTwoDecorators(a = %s, b = %s, c = %s)" % (a, b, c) 

functionWithOneDecorator(1, 2, 3) 
functionWithOneDecorator(1, b=2, c=3) 
functionWithOneDecorator(a=1, b=2, c=3) 
functionWithOneDecorator(c=3, b=2, a=1) 

functionWithTwoDecorators(1, 2, 3) 
functionWithTwoDecorators(1, b=2, c=3) 
functionWithTwoDecorators(a=1, b=2, c=3) 
functionWithTwoDecorators(c=3, b=2, a=1)  

Molto più pulito, e funziona!

+1

Perché 'args [0], * args [1:]', è lo stesso di '* args'? –

+0

Che problema stai cercando di risolvere con questo decoratore? Per quanto ne so, il suo obiettivo principale sembra essere quello di assicurarsi che il primo argomento dato - parola chiave/facoltativo o altro - sia sempre passato alla funzione avvolta in quanto è l'argomento "primo". Inoltre, qual è il significato previsto dell'argomento 'id' per il decoratore? Non è usato da nessuna parte. –

+0

Voglio applicare una conversione al primo argomento. Nel codice fornito sopra ho escluso il codice che esegue la conversione in modo da non distrarre dal problema. –

risposta

5

Il problema è che la firma della funzione decorata non è la firma (getargspec) dell'originale. È davvero ben spiegato nell'aiuto dello decorator module che puoi risolvere il tuo problema. Fondamentalmente, dovresti usare un decoratore che conserva la firma, in modo che il secondo decoratore veda la stessa firma del primo.

+0

Brillante, è esattamente quello che stavo cercando. –

Problemi correlati