2009-03-09 9 views
4

Si j'ai la fonction suivante:Comment puis-je utiliser des arguments nommés dans un décorateur?


def intercept(func): 
    # do something here 

@intercept(arg1=20) 
def whatever(arg1,arg2): 
    # do something here 

Je voudrais pour intercepter au feu uniquement lorsque arg1 est 20. Je voudrais être en mesure de passer des paramètres nommés à la fonction. Comment pourrais-je accomplir cela?

Voici un petit exemple de code:



def intercept(func): 
    def intercepting_func(*args,**kargs): 
     print "whatever" 
     return func(*args,**kargs) 
    return intercepting_func 

@intercept(a="g") 
def test(a,b): 
    print "test with %s %s" %(a,b) 

test("g","d") 

Cette lance le TypeError d'exception suivante: interception() a obtenu un argument de mot-clé inattendu 'a'

Répondre

7
from functools import wraps 

def intercept(target,**trigger): 
    def decorator(func): 
     names = getattr(func,'_names',None) 
     if names is None: 
      code = func.func_code 
      names = code.co_varnames[:code.co_argcount] 
     @wraps(func) 
     def decorated(*args,**kwargs): 
      all_args = kwargs.copy() 
      for n,v in zip(names,args): 
       all_args[n] = v 
      for k,v in trigger.iteritems(): 
       if k in all_args and all_args[k] != v: 
        break 
      else: 
       return target(all_args) 
      return func(*args,**kwargs) 
     decorated._names = names 
     return decorated 
    return decorator 

Exemple:

def interceptor1(kwargs): 
    print 'Intercepted by #1!' 

def interceptor2(kwargs): 
    print 'Intercepted by #2!' 

def interceptor3(kwargs): 
    print 'Intercepted by #3!' 

@intercept(interceptor1,arg1=20,arg2=5) # if arg1 == 20 and arg2 == 5 
@intercept(interceptor2,arg1=20)  # elif arg1 == 20 
@intercept(interceptor3,arg2=5)   # elif arg2 == 5 
def foo(arg1,arg2): 
    return arg1+arg2 

>>> foo(3,4) 
7 
>>> foo(20,4) 
Intercepted by #2! 
>>> foo(3,5) 
Intercepted by #3! 
>>> foo(20,5) 
Intercepted by #1! 
>>> 

functools.wraps fait ce que le "décorateur simple" sur le wiki fait; Mises à jour __doc__, __name__ et autre attribut du décorateur.

+0

Donc, il n'y a pas un moyen d'appliquer cela à n'importe quelle fonction? – Geo

+0

Retravaillé la réponse. –

3

Vous pouvez le faire en utilisant args * et ** kwargs dans le décorateur:

def intercept(func, *dargs, **dkwargs): 
    def intercepting_func(*args, **kwargs): 
     if (<some condition on dargs, dkwargs, args and kwargs>): 
      print 'I intercepted you.' 
     return func(*args, **kwargs) 
    return intercepting_func 

Il est à vous comment vous souhaitez passer des arguments pour contrôler le comportement du décorateur.

Pour que cela soit aussi transparent que possible à l'utilisateur final, vous pouvez utiliser le "décorateur simple" sur "decorator decorator"

+0

Oui, mais je veux seulement l'intercepter quand la condition est remplie. – Geo

+0

Vous pouvez accéder aux arguments de la fonction, selon mon exemple modifié. Ensuite, agissez en fonction de ce qu'ils sont. – zweiterlinde

+1

Dans mon exemple de code ci-dessus, j'ai l'exception suivante: intercept() a un mot-clé inattendu arg1 – Geo

11

du Python wiki ou Michele Simionato Rappelez-vous que

@foo 
def bar(): 
    pass 

équivaut à:

def bar(): 
    pass 
bar = foo(bar) 

donc si vous faites:

@foo(x=3) 
def bar(): 
    pass 

qui est équivalent à:

def bar(): 
    pass 
bar = foo(x=3)(bar) 

afin que votre décorateur doit ressembler à ceci:

def foo(x=1): 
    def wrap(f): 
     def f_foo(*args, **kw): 
      # do something to f 
      return f(*args, **kw) 
     return f_foo 
    return wrap 

En d'autres termes, def wrap(f) est vraiment le décorateur et foo(x=3) est un appel de fonction que le rendement un décorateur.