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'

¿Fue útil?

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 "

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top