2010-10-14 21 views
25

Me gustaría hacer un decorador que podría ser utilizado con o sin un parámetro: Algo como esto:¿Cómo construir un decorador con parámetros opcionales?

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' 

En mi código, sólo el uso de decorador con el parámetro está trabajando: ¿Cómo proceder para tener ambos trabajando (con y sin parámetro)?

Respuesta

30

me encontré con un ejemplo, puede utilizar @trace o @trace('msg1','msg2'): agradable!

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

Supongo que funciona para su caso y lo haría en casos similares. Pero, ¿y si el argumento del decorador es en realidad una sola invocación? ¿Cómo diferenciaría usted entre la función decorada y el argumento? – ksrini

+1

@ksrini: Ignacio lo señaló en su [respuesta] (http://stackoverflow.com/a/3931654/355230) años atrás. – martineau

+2

También puede evitar esto usando argumentos de palabras clave (como '@trace (default = ...)'). – jtbandes

4

Tiene que detectar si el argumento para el decorador es una función, y usar un decorador simple en ese caso. Y luego necesita esperar que nunca necesite pasar solo una función al decorador parametrizado.

16

Si usted quiere tomar parámetros a su decorador, es necesario siempre llamar como una función:

@d() 
def func(): 
    pass 

De lo contrario, es necesario tratar de detectar la diferencia en los parámetros - en otras palabras, , necesitas adivinar mágicamente lo que quiere decir la persona que llama. No crees una API que necesite adivinar; consistentemente di lo que quieres decir con comenzar.

En otras palabras, una función debe ser un decorador o una decoradora; no debería ser ambos.

Tenga en cuenta que si todo lo que quiere hacer es almacenar un valor, no necesita escribir una clase.

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

Esto es probablemente un buen consejo, pero no es correcto que una función no pueda hacer ambas cosas, como explica Ignacio Vazquez-Abrams. Probablemente sea mejor explicar eso en la respuesta. –

+3

@Muhammad: No dije que no, le dije que no debería. –

+1

Entiendo. Pero el valor de la respuesta sería mayor si este punto se explica un poco mejor. Solo digo. –

2

Esto funcionaría.

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 un @invocation función decoradora no se pasa ningún argumento explícitas, se llama a la función definida en la siguiente def. Si es pasado argumentos, entonces primero se llama con ellos y luego el resultado de esa llamada preliminar (que también debe ser invocable) se llama con la función que se está definiendo. De cualquier forma, el valor de retorno de la última o única llamada está vinculado al nombre de la función definida.

+0

Excelente, ¡qué respuesta! – adkl

9

Si no le importa confiar en el uso de argumentos con nombre, hice algo similar a lo que necesita:

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 

Esto funciona porque sólo un argumento que no es la palabra clave, la función decorada se pasa al decorador.

Observe que también utilicé los argumentos pasados ​​a la función decorada, en este caso 'uno mismo'.

Cuestiones relacionadas