Decoradores en Python (II) - Decoradores con parámetros
Este post fue migrado de un blog hecho con Wordpress. Si se ve mal, dejame un comentario y lo arreglo.
Un año después del primer artículo, llega el segundo. ¿Por qué tardó tanto? Por lo general mis artículos técnicos surgen de algún problema que se me presenta y para el cual necesito investigar antes de poder solucionarlo. El artículo anterior cubría todo lo que necesité hacer con decoradores en Python hasta el mes pasado, cuando necesité decoradores con parámetros.
Si no leíste el artículo anterior, te recomiendo que lo hagas antes de seguir: Decoradores en Python (I).
Decoradores con Parámetros
Cuando quise escribir un decorador con un parámetro me encontré con errores que ni siquiera entendía. No solo que los estaba escribiendo mal, sino que también los estaba usando mal. Te voy a evitar el sufrimiento.
Un decorador con parámetro se aplica así (siendo deco un decorador y 1 el argumento utilizado):
@deco(1) def funcion_a_decorar(a, b, c): pass
Creo que la raíz de mi confusión fue el azúcar sintáctica (si, el @). Así que vamos a sacarlo y ver cómo se usaría este decorador en una versión de Python más vieja:
def funcion_a_decorar(a, b, c): pass
funcion_a_decorar = deco(1)(funcion_a_decorar)
Esto luce más claro para mi: deco es llamado con un argumento y el resultado tiene que ser algún objeto que pueda ser llamado con una función como parámetro para... decorarla. ¿Se entiende la idea? Vamos a definir deco, va a recibir un parámetro y utilizarlo para crear un decorador como los del artículo anterior. Finalmente retorna este decorador interno.
Agreguemos semántica al ejemplo. Mi decorador con parámetro recibirá un número, este número se usará para indicar cuantas veces queremos ejecutar la función decorada.
def deco(i): def _deco(f): def inner(*args, **kwargs): for n in range(i): r = f(*args, **kwargs) return r return inner return _deco
Como una convención personal, uso para el nombre de la segunda función _{nombre de la primer funcion}. Notemos entonces que _deco es un decorador dinámico, dependiendo del parámetro i, la función inner se compilará de una forma o de otra. Apliquemos el decorador:
@deco(2) def saluda(nombre): print "hola", nombre
>>> saluda("juanjo") hola juanjo hola juanjo
@deco(3) def suma1(): global n n += 1
>>> n = 0 >>> suma1() >>> n 3
Cuando aplicamos deco, se ejecuta deco, se compila _deco, se aplica _deco a la función que definimos y se compila inner utilizando un valor dado para i. Cuando llamamos a nuestra función (saluda, o suma1, en los ejemplos) se ejecuta inner.
¡Espero que se haya entendido!
Si no...
Si en lo anterior no fui lo suficientemente claro (por favor quejate en un comentario), no todo está perdido. Te puedo entregar un decorador para decoradores que convierte a tu decorador en un decorador con parámetros. ¿Qué tal?
def decorador_con_parametros(d): def decorador(*args, **kwargs): def inner(func): return d(func, *args, **kwargs) return inner return decorador
Original usando lambda en http://pre.activestate.com/recipes/465427/
Se usa así:
@decorador_con_parametros def deco(func, i): def inner(*args, **kwargs): for n in range(i): r = func(*args, **kwargs) return r return inner
@deco(2) def saludar(nombre): print "chau", nombre
>>> saludar("juanjo") chau juanjo chau juanjo
Para la próxima
Para el próximo artículo voy a explorar utilizar clases decoradoras en lugar de funciones decoradoras. Si bien todavía no lo terminé de investigar, me parece un enfoque que permite escribir código más organizado. Veremos! update: aquí está.
Comentarios
Comments powered by Disqus