2010-10-14 2 views
25

Я хотел бы сделать декоратор, который может быть использован с или без параметра: Что-то вроде этого:Как построить декоратор с дополнительными параметрами?

class d(object): 
    def __init__(self,msg='my default message'): 
     self.msg = msg 
    def __call__(self,fn): 
     def newfn(): 
      print self.msg 
      return fn() 
     return newfn 

@d('This is working') 
def hello(): 
    print 'hello world !' 

@d 
def too_bad(): 
    print 'does not work' 

В моем коде, только использование декоратора с параметром работает: Как действовать, чтобы оба работают (с параметром и без него)?

ответ

30

Я нашел пример, вы можете использовать @trace или @trace('msg1','msg2'): хорошо!

def trace(*args): 
    def _trace(func): 
     def wrapper(*args, **kwargs): 
      print enter_string 
      func(*args, **kwargs) 
      print exit_string 
     return wrapper 
    if len(args) == 1 and callable(args[0]): 
     # No arguments, this is the decorator 
     # Set default values for the arguments 
     enter_string = 'entering' 
     exit_string = 'exiting' 
     return _trace(args[0]) 
    else: 
     # This is just returning the decorator 
     enter_string, exit_string = args 
     return _trace 
+0

Я думаю, это работает для вашего дела и будет для подобных случаев. Но что, если аргумент декоратора на самом деле является единственным вызываемым? Как бы вы различали декорированную функцию и аргумент? – ksrini

+1

@ksrini: Игнасио указал это в своем [ответе] (http://stackoverflow.com/a/3931654/355230) лет назад. – martineau

+2

Вы также можете обойти это, используя аргументы ключевых слов (например, '@trace (default = ...)'). – jtbandes

4

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

16

Если вы хотите принять параметры вашего декоратора, вам нужно всегда называть его как функцию:

@d() 
def func(): 
    pass 

В противном случае, вам нужно, чтобы попытаться обнаружить разницу в параметрах - другими словами, , вам нужно магически угадать, что означает вызывающий. Не создавайте API, который нужно угадать; последовательно говорите, что вы хотите для начала.

Другими словами, функция должна быть либо декоратором, либо фабрикой декоратора; это не должно быть и то, и другое.

Обратите внимание, что если все, что вы хотите сделать, это сохранить значение, вам не нужно писать класс.

def d(msg='my default message'): 
    def decorator(func): 
     def newfn(): 
      print msg 
      return func() 
     return newfn 
    return decorator 

@d('This is working') 
def hello(): 
    print 'hello world !' 

@d() 
def hello2(): 
    print 'also hello world' 
+1

Это, вероятно, хороший совет, но неверно, что функция не может сделать то и другое, как объясняет Игнасио Васкес-Абрамс. Вероятно, лучше объяснить это в ответе. –

+3

@ Мухаммад: Я не сказал, что не может, я сказал, что это не должно. –

+1

Я понимаю. Но значение ответа было бы выше, если бы этот момент был объяснен немного лучше. Просто говорю. –

2

Это будет работать.

def d(arg): 
    if callable(arg): 
     def newfn(): 
      print 'my default message' 
      return arg() 
     return newfn 
    else: 
     def d2(fn): 
      def newfn(): 
       print arg 
       return fn() 
      return newfn 
     return d2 

@d('This is working') 
def hello(): 
    print 'hello world !' 

@d 
def hello2(): 
    print 'hello2 world !' 

@d('Applying it twice') 
@d('Would also work') 
def hello3(): 
    print 'hello3 world !' 

hello() 
hello2() 
hello3() 

# output 
# 
# This is working 
# hello world ! 
# my default message 
# hello2 world ! 
# Applying it twice 
# Would also work 
# hello3 world ! 

Если функция декоратор @invocation не передается никаких явных аргументов, то вызывается функция, определенная в следующем def. Если он равен, то передаются аргументы, затем он сначала вызывается с ними, а затем результат , что предварительный вызов вызывается с вызываемой функцией. В любом случае возвращаемое значение последнего или единственного вызова привязано к определенному имени функции.

+0

Отлично, какой ответ! – adkl

9

Если вы не возражаете, опираясь на использование именованных аргументов, я сделал что-то похожее на то, что вам нужно:

def cached_property(method=None, get_attribute=lambda a: '_%s_cached' % (a,)): 
    """ 
    Caches an object's attribute. 

    Can be used in the following forms: 
    @cached_property 
    @cached_property() 
    @cached_property(get_attribute=lambda x: 'bla') 

    @param method: the method to memoizes 
    @param get_attribute: a callable that should return the cached attribute 
    @return a cached method 
    """ 
    def decorator(method): 
     def wrap(self): 
      private_attribute = get_attribute(method.__name__) 
      try: 
       return getattr(self, private_attribute) 
      except AttributeError: 
       setattr(self, private_attribute, method(self)) 
       return getattr(self, private_attribute) 
     return property(wrap) 
    if method: 
     # This was an actual decorator call, ex: @cached_property 
     return decorator(method) 
    else: 
     # This is a factory call, ex: @cached_property() 
     return decorator 

Это работает, потому что только один не аргумент ключевое слово, функция украшен передается декоратора.

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

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