2011-10-09 10 views
10

Estoy escribiendo un decorador de funciones que aplicará una conversión al primer argumento de la función. Funciona bien si solo decorar mis funciones una vez, pero si las decorar dos veces me sale un error. A continuación se muestra un código que demuestra el problema, es una versión simplificada del código en el que estoy trabajando. He excluido el código que hace la conversión a fin de no desviar la atención del problemaDecoradores de funciones anidadas que operan en argumentos en python

from inspect import getargspec 
from functools import wraps 

def dec(id): 
    def _dec(fn): 
     @wraps(fn) 
     def __dec(*args, **kwargs): 
      if len(args): 
       return fn(args[0], *args[1:], **kwargs) 
      else: 
       first_arg = getargspec(fn).args[0] 
       new_kwargs = kwargs.copy() 
       del new_kwargs[first_arg] 
       return fn(kwargs[first_arg], **new_kwargs) 
     return __dec 
    return _dec 

@dec(1) 
def functionWithOneDecorator(a, b, c): 
    print "functionWithOneDecorator(a = %s, b = %s, c = %s)" % (a, b, c) 

@dec(1) 
@dec(2) 
def functionWithTwoDecorators(a, b, c): 
    print "functionWithTwoDecorators(a = %s, b = %s, c = %s)" % (a, b, c) 

functionWithOneDecorator(1, 2, 3) 
functionWithOneDecorator(1, b=2, c=3) 
functionWithOneDecorator(a=1, b=2, c=3) 
functionWithOneDecorator(c=3, b=2, a=1) 

functionWithTwoDecorators(1, 2, 3) 
functionWithTwoDecorators(1, b=2, c=3) 
functionWithTwoDecorators(a=1, b=2, c=3) 
functionWithTwoDecorators(c=3, b=2, a=1) 

Cuando ejecuto el código anterior me sale el siguiente resultado:

functionWithOneDecorator(a = 1, b = 2, c = 3) 
functionWithOneDecorator(a = 1, b = 2, c = 3) 
functionWithOneDecorator(a = 1, b = 2, c = 3) 
functionWithOneDecorator(a = 1, b = 2, c = 3) 
functionWithTwoDecorators(a = 1, b = 2, c = 3) 
functionWithTwoDecorators(a = 1, b = 2, c = 3) 
IndexError: list index out of range 

Esto es porque cuando el segundo decorador inspecciona la función que está decorando para encontrar los nombres de los argumentos y falla porque está decorando un decorador y eso solo toma * args y ** kwargs.

Puedo pensar en formas de solucionar el problema que funcionaría en el código anterior, pero aún se rompería si una función estuviera decorada con mi decorador y otra de un tercero. ¿Hay una manera general de solucionar esto? o hay una mejor manera de lograr el mismo resultado?

Actualización: Gracias a @Hernan por señalar el decorator module. Resuelve este problema exactamente. Ahora mi código es el siguiente:

from decorator import decorator 

def dec(id): 
    @decorator 
    def _dec(fn, *args, **kwargs): 
     return fn(args[0], *args[1:], **kwargs) 
    return _dec 

@dec(1) 
def functionWithOneDecorator(a, b, c): 
    print "functionWithOneDecorator(a = %s, b = %s, c = %s)" % (a, b, c) 

@dec(1) 
@dec(2) 
def functionWithTwoDecorators(a, b, c): 
    print "functionWithTwoDecorators(a = %s, b = %s, c = %s)" % (a, b, c) 

functionWithOneDecorator(1, 2, 3) 
functionWithOneDecorator(1, b=2, c=3) 
functionWithOneDecorator(a=1, b=2, c=3) 
functionWithOneDecorator(c=3, b=2, a=1) 

functionWithTwoDecorators(1, 2, 3) 
functionWithTwoDecorators(1, b=2, c=3) 
functionWithTwoDecorators(a=1, b=2, c=3) 
functionWithTwoDecorators(c=3, b=2, a=1)  

Mucho más limpio, y funciona!

+1

¿Por qué 'args [0], * args [1:]', es lo mismo que '* args'? –

+0

¿Qué problema estás tratando de resolver con este decorador? Por lo que puedo decir, su principal objetivo parece ser asegurarse de que el primer argumento dado, palabra clave/opcional u otro, siempre se pase a la función envuelta ya que es el "primer" argumento. Además, ¿cuál es el significado previsto del argumento 'id' para el decorador? No se usa en ninguna parte. –

+0

Quiero aplicar una conversión al primer argumento. En el código proporcionado anteriormente, excluí el código que hace la conversión para no distraerme del problema. –

Respuesta

5

El problema es que la firma de su función decorada no es la firma (getargspec) del original. Está muy bien explicado en la ayuda del decorator module que puede resolver su problema. Básicamente, debe usar decoradores que conserven sus firmas, para que el segundo decorador vea la misma firma que el primero.

+0

Brillante, eso es exactamente lo que estaba buscando. –

Cuestiones relacionadas