2010-10-14 10 views
25

Je voudrais faire un décorateur qui pourrait être utilisé avec ou sans paramètre: Quelque chose comme ceci:Comment construire un décorateur avec des paramètres optionnels?

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' 

Dans mon code, seule l'utilisation du décorateur avec le paramètre travaille: Comment procéder aux les deux fonctionnant (avec et sans paramètre)?

Répondre

30

Je trouve un exemple, vous pouvez utiliser @trace ou @trace('msg1','msg2'): nice!

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

Je suppose que cela fonctionne pour votre cas et le ferait pour des cas similaires. Mais que faire si l'argument du décorateur est en réalité un seul appelable? Comment feriez-vous la différence entre la fonction décorée et l'argument? – ksrini

+1

@ksrini: Ignacio l'a signalé dans sa [réponse] (http://stackoverflow.com/a/3931654/355230) il y a des années. – martineau

+2

Vous pouvez également contourner ce problème en utilisant des arguments de mots-clés (tels que '@trace (default = ...)'). – jtbandes

4

Vous devez détecter si l'argument du décorateur est une fonction et utiliser un simple décorateur dans ce cas. Et puis vous devez espérer que vous n'aurez jamais besoin de transmettre seulement une fonction au décorateur paramétré.

16

Si vous voulez prendre des paramètres à votre décorateur, vous devez toujours appeler en fonction:

@d() 
def func(): 
    pass 

Sinon, vous devez essayer de détecter la différence dans les paramètres - en d'autres termes , vous devez deviner par magie ce que l'appelant veut dire. Ne créez pas une API à deviner. toujours dire ce que vous voulez dire pour commencer. En d'autres termes, une fonction doit être soit un décorateur, soit une usine de décorateur; ça ne devrait pas être les deux. Notez que si tout ce que vous voulez faire est de stocker une valeur, vous n'avez pas besoin d'écrire une classe.

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

Ceci est probablement un bon conseil, mais il n'est pas correct qu'une fonction ne puisse pas faire les deux, comme l'explique Ignacio Vazquez-Abrams. Il est probablement préférable de l'expliquer dans la réponse. –

+3

@Muhammad: Je n'ai pas dit que ça ne pouvait pas, j'ai dit que ça ne devrait pas. –

+1

Je comprends. Mais la valeur de la réponse serait plus élevée si ce point est expliqué un peu mieux. Je dis juste. –

2

Cela fonctionnerait.

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 ! 

Si une fonction décorateur @invocation ne reçoit aucun argument explicite, il est appelé à la fonction définie dans la def suivante. Si est arguments passés, alors il est d'abord appelé avec eux puis le résultat appel préliminaire (qui doit lui aussi être un appelable) est appelé avec la fonction en cours de définition. Dans les deux cas, la valeur de retour du dernier ou unique appel est liée au nom de la fonction définie.

+0

Excellent, quelle réponse! – adkl

9

Si vous ne me dérange pas compter sur l'aide d'arguments nommés, je fait quelque chose de semblable à ce que vous avez besoin:

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 

Cela fonctionne parce qu'un seul argument non mot-clé, la fonction décorée est passée au décorateur.

Notez que j'ai également utilisé les arguments passés à la fonction décorée, dans ce cas 'self'.

Questions connexes