2010-01-19 10 views
8

¿Por qué los dos guiones siguientes no son equivalentes?¿Por qué los decoradores de Python no pueden encadenarse entre las definiciones?

(tomado de otra pregunta: Understanding Python Decorators)

def makebold(fn): 
    def wrapped(): 
     return "<b>" + fn() + "</b>" 
    return wrapped 

def makeitalic(fn): 
    def wrapped(): 
     return "<i>" + fn() + "</i>" 
    return wrapped 

@makebold 
@makeitalic 
def hello(): 
    return "hello world" 

print hello() ## returns <b><i>hello world</i></b> 

y decorada con un decorador:

def makebold(fn): 
    def wrapped(): 
     return "<b>" + fn() + "</b>" 
    return wrapped 

@makebold 
def makeitalic(fn): 
    def wrapped(): 
     return "<i>" + fn() + "</i>" 
    return wrapped 

@makeitalic 
def hello(): 
    return "hello world" 

print hello() ## TypeError: wrapped() takes no arguments (1 given) 

¿Por qué quiero saber? He escrito un decorador retry para capturar las excepciones de MySQLdb: si la excepción es transitoria (por ejemplo, tiempo de espera) volverá a llamar a la función después de dormir un poco.

También tengo un decorador modifies_db que se encarga de algunas tareas de limpieza relacionadas con la caché. modifies_db está decorado con retry, por lo que supuse que todas las funciones decoradas con modifies_db también volverían a intentarse implícitamente. ¿Qué hice mal?

+0

Buena pregunta. Me encontré con el mismo escenario hace unos meses mientras hacía mi propio decorador de reintentos. Requirió un recuento de reintentos, por lo que el problema se presentó un poco diferente al suyo, pero tenía las mismas soluciones que se muestran a continuación. –

Respuesta

9

El problema con el segundo ejemplo es que

@makebold 
def makeitalic(fn): 
    def wrapped(): 
     return "<i>" + fn() + "</i>" 
    return wrapped 

está tratando de decorar makeitalic, el decorador, y no wrapped, la función que devuelve.

Puede hacer lo que creo que la intención con algo como esto:

def makeitalic(fn): 
    @makebold 
    def wrapped(): 
     return "<i>" + fn() + "</i>" 
    return wrapped 

Aquí makeitalic utiliza para decorar makeboldwrapped.

+2

+1 Esto indica exactamente el problema y cómo debe hacerse en su lugar. Básicamente, el problema proviene de pensar que los decoradores funcionan con la composición, pero no lo hacen. –

+0

Sí, eso es exactamente lo que estaba tratando de hacer, y sí, entendí mal que los decoradores == composición funcional – RobM

1

La razón es porque wrapped() dentro de makebold no acepta ningún argumento.

Cuando usa el decorador de esa manera puede causar algunos problemas, le voy a publicar un ejemplo de cómo lograr lo que quiere, déme un momento.

Aquí hay un ejemplo de lo que necesita.

def makebold(rewrap=False): 
    if rewrap: 
     def inner(decorator): 
      def rewrapper(func): 
       def wrapped(*args, **kwargs): 
        return "<b>%s</b>" % decorator(func)(*args,**kwargs) 
       return wrapped 
      return rewrapper 
     return inner 

    else: 
     def inner(func): 
      def wrapped(*args, **kwargs): 
       return "<b>%s</b>" % func(*args, **kwargs)  
      return wrapped 
     return inner 

@makebold(rewrap=True) 
def makeitalic(fn): 
    def wrapped(*args, **kwargs): 
     return "<i>%s</i>" % fn(*args, **kwargs) 
    return wrapped 

@makeitalic 
def hello(): 
    return "hello world" 

@makebold() 
def hello2(): 
    return "Bob Dole"  

if __name__ == "__main__": 
    print hello() 
    print hello2() 

makebold es un poco feo, pero que muestra cómo escribir un decorador que puede envolver opcionalmente otro decorador.

Aquí está la salida del script anterior:

<b><i>hello world</i></b> 
<b>Bob Dole</b> 

Tenga en cuenta que es el único makebold decorador recursiva. También tenga en cuenta la sutil diferencia en el uso: @makebold() vs @makeitalic.

+0

-1 ... demasiado complejo, y "makeitalic" es un nombre incorrecto para algo que sea negrita + cursiva.Ver [esta respuesta] (http://stackoverflow.com/a/739665/850830) que hace lo mismo en unas pocas líneas de código. – Ryan

0

El problema es reemplazar "makeitalic" (que toma un argumento) con la función "wrapped" en "makebold" que toma cero argumentos.

Uso *args, **kwargs para pasar en argumentos más abajo en la cadena:

def wrapped(*args, **kwargs): 
    return "<b>" + fn(*args, **kwargs) + "</b>" 
+0

el problema es un poco más severo que eso en realidad. envolver a otro decorador tiene algunos problemas más. –

Cuestiones relacionadas