2012-04-16 3 views
64

У меня есть декоратор, как показано нижеКак передать дополнительные аргументы в python decorator?

def myDecorator(test_func): 
    return callSomeWrapper(test_func) 
def callSomeWrapper(test_func): 
    return test_func 
@myDecorator 
def someFunc(): 
    print 'hello' 

Я хочу, чтобы усилить этот декоратор, чтобы принять еще один аргумент, как показано ниже

def myDecorator(test_func,logIt): 
    if logIt: 
     print "Calling Function: " + test_func.__name__ 
    return callSomeWrapper(test_func) 
@myDecorator(False) 
def someFunc(): 
    print 'Hello' 

Но этот код выдает ошибку,

TypeError: myDecorator() takes exactly 2 arguments (1 given) 

Почему функция автоматически не передается? Как явным образом передать функцию функции декоратора?

+2

Балка: пожалуйста, не используйте логическое значение в качестве аргумента, это не Г.Д. подход и уменьшить Кодекс readliability –

+7

@KitHo - это логический флаг, поэтому, используя логическое значение, это правильный подход. – AKX

+0

@KitHo - что такое «gd»? Это хорошо"? –

ответ

107

Поскольку вы звоните декоратор, как функцию, она должна возвращать другую функцию, которая является фактическим декоратор:

def my_decorator(param): 
    def actual_decorator(func): 
     print("Decorating function {}, with parameter {}".format(func.__name__, param)) 
     return function_wrapper(func) # assume we defined a wrapper somewhere 
    return actual_decorator 

Внешняя функция будет дана любые аргументы, которые вы пройти в явном виде, и он должен возвращать внутренний функция. Внутренняя функция будет передана функции для украшения и возврата измененной функции.

Обычно вы хотите, чтобы декоратор изменил поведение функции, обернув его в функцию обертки. Вот пример, который, возможно, добавляет регистрацию, когда функция вызывается:

def log_decorator(log_enabled): 
    def actual_decorator(func): 
     @functools.wraps(func) 
     def wrapper(*args, **kwargs): 
      if log_enabled: 
       print("Calling Function: " + func.__name__) 
      return func(*args, **kwargs) 
     return wrapper 
    return actual_decorator 

В functools.wraps копирует вызов вещи, как имя и строку документации для функции обертки, чтобы сделать его более похожим на исходную функцию. Использование

Пример:

>>> @log_decorator(True) 
... def f(x): 
...  return x+1 
... 
>>> f(4) 
Calling Function: f 
5 
+10

И использование ['functools.wraps'] (http://docs.python.org/library/functools.html#functools.wraps) рекомендуется - оно сохраняет исходное имя, docstring и т. Д. Завернутой функции. – AKX

+0

@AKX: Спасибо, я добавил это ко второму примеру. – interjay

+1

Так что в основном декоратор всегда принимает только один аргумент, который является функцией. Но декоратор может быть возвращаемым значением функции, которая может принимать аргументы. Это верно? – balki

34

Просто, чтобы обеспечить различные точки зрения: синтаксис

@expr 
def func(...): #stuff 

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

def func(...): #stuff 
func = expr(func) 

В частности, expr может быть все, что угодно, до тех пор, пока он оценивает вызываемый. В особенность особенность, expr может быть фабрикой декоратора: вы даете ему некоторые параметры, и это дает вам декоратор. Так, может быть, лучший способ понять вашу ситуацию как

dec = decorator_factory(*args) 
@dec 
def func(...): 

, который затем может быть сокращен до

@decorator_factory(*args) 
def func(...): 

Конечно, так как выглядит как decorator_factory является декоратор, люди склонны называть его чтобы отразить это. Это может сбивать с толку, когда вы пытаетесь следовать уровням косвенности.

14

Просто хочу добавить полезный трюк, который позволит сделать аргументы декоратора необязательными. Он также будет использовать повторное использование декоратора и уменьшение гнездования

import functools 

def myDecorator(test_func=None,logIt=None): 
    if not test_func: 
     return functools.partial(myDecorator, logIt=logIt) 
    @functools.wraps(test_func) 
    def f(*args, **kwargs): 
     if logIt==1: 
      print 'Logging level 1 for {}'.format(test_func.__name__) 
     if logIt==2: 
      print 'Logging level 2 for {}'.format(test_func.__name__) 
     return test_func(*args, **kwargs) 
    return f 

#new decorator 
myDecorator_2 = myDecorator(logIt=2) 

@myDecorator(logIt=2) 
def pow2(i): 
    return i**2 

@myDecorator 
def pow3(i): 
    return i**3 

@myDecorator_2 
def pow4(i): 
    return i**4 

print pow2(2) 
print pow3(2) 
print pow4(2) 
1

Просто еще один способ сделать декораторы. Я нахожу этот способ самым легким, чтобы обернуть мою голову.

import functools 

class NiceDecorator: 
    def __init__(self, param_foo='a', param_bar='b'): 
     self.param_foo = param_foo 
     self.param_bar = param_bar 

    def __call__(self, func): 
     @functools.wraps(func) 
     def my_logic(*args, **kwargs): 
      # whatever logic your decorator is supposed to implement goes in here 
      print('pre action baz') 
      print(self.param_bar) 
      # including the call to the decorated function (if you want to do that) 
      result = func(*args, **kwargs) 
      print('post action beep') 
      return result 

     return my_logic 

# usage example from here on 
@NiceDecorator(param_bar='baaar') 
def example(): 
    print('example yay') 


example() 
Смежные вопросы