2009-03-09 3 views
4

Если у меня есть следующие функции:Как использовать именованные аргументы в декораторе?


def intercept(func): 
    # do something here 

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

Я хотел бы для перехвата стрелять только тогда, когда arg1 20. Я хотел бы иметь возможность передавать именованные параметры функции. Как я мог это сделать?

Вот небольшой пример кода:



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") 

Это бросает следующее исключение TypeError: перехватывать() получила неожиданный аргумент ключевого слова 'а'

ответ

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 

Пример:

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 делает то, что «простой декоратор» на wiki делает; Обновления __doc__, __name__ и другие атрибуты декоратора.

+0

Итак, нет способа применить это к любой функции? – Geo

+0

Переработал ответ. –

3

Вы можете сделать это с помощью * арг и ** kwargs в декоратора:

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 

Это до вас, как вы хотите передать в качестве аргументов для управления поведением декоратора.

Чтобы сделать это как можно более прозрачным для конечного пользователя, вы можете использовать «простой декоратор» на "decorator decorator"

+0

Да, но я хочу только перехватить его, когда условие выполнено. – Geo

+0

Вы можете получить доступ к аргументам функции в моем отредактированном примере. Затем выполните действия, основанные на том, что они есть. – zweiterlinde

+1

В моем примере кода выше, я получаю следующее исключение: intercept() получил неожиданное ключевое слово arg1 – Geo

11

в Python wiki или Мишель Симионато помнить, что

@foo 
def bar(): 
    pass 

эквивалентно:

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

так что если вы делаете:

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

, что эквивалентно:

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

так что ваш декоратор должен выглядеть примерно так:

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 

Другими словами, def wrap(f) действительно декоратор, и foo(x=3) является вызов функции, которая возвращает декоратор.

Смежные вопросы