Comment utiliser des arguments nommés dans un décorateur?
-
06-07-2019 - |
Question
Si j'ai la fonction suivante:
def intercept(func):
# do something here
@intercept(arg1=20)
def whatever(arg1,arg2):
# do something here
J'aimerais que l'interception ne se déclenche que lorsque arg1 vaut 20. J'aimerais pouvoir 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")
Ceci lève l'exception suivante TypeError: intercept () a obtenu un argument de mot clé inattendu 'a'
La solution
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 "simple décorateur" " sur le wiki fait; Met à jour __ doc __
, __ nom __
et un autre attribut du décorateur.
Autres conseils
N'oubliez pas que
@foo
def bar():
pass
est équivalent à:
def bar():
pass
bar = foo(bar)
donc si vous le faites:
@foo(x=3)
def bar():
pass
cela équivaut à:
def bar():
pass
bar = foo(x=3)(bar)
votre décorateur doit donc 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 qui renvoie un décorateur.
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
C'est à vous de décider comment vous souhaitez passer des arguments pour contrôler le comportement du décorateur.
Pour que cela soit aussi transparent que possible pour l'utilisateur final, vous pouvez utiliser le "simple décorateur". sur le wiki Python ou le " décorateur décorateur "