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'

Était-ce utile?

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 "

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top