¿Cómo puedo usar argumentos con nombre en un decorador?
-
06-07-2019 - |
Pregunta
Si tengo la siguiente función:
def intercept(func):
# do something here
@intercept(arg1=20)
def whatever(arg1,arg2):
# do something here
Me gustaría que la intercepción se active solo cuando arg1 sea 20. Me gustaría poder pasar parámetros con nombre a la función. ¿Cómo podría lograr esto?
Aquí hay un pequeño ejemplo de código:
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")
Esto arroja la siguiente excepción TypeError: intercept () obtuvo un argumento de palabra clave inesperado 'a'
Solución
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
Ejemplo:
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
hace lo que el " decorador simple " en la wiki hace; Actualiza __doc__
, __name__
y otros atributos del decorador.
Otros consejos
Recuerda eso
@foo
def bar():
pass
es equivalente a:
def bar():
pass
bar = foo(bar)
así que si lo haces:
@foo(x=3)
def bar():
pass
eso es equivalente a:
def bar():
pass
bar = foo(x=3)(bar)
por lo que su decorador debe verse así:
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 otras palabras, def wrap (f)
es realmente el decorador, y foo (x = 3)
es una llamada de función que devuelve un decorador.
Puede hacer esto usando * args y ** kwargs en el decorador:
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
Depende de usted cómo desea pasar los argumentos para controlar el comportamiento del decorador.
Para hacer esto lo más transparente posible para el usuario final, puede usar el "decorador simple" en el Python wiki o Michele Simionato's " decorator decorator "