Aplicar un decorador a todas las funciones de un módulo en Python

Este post fue migrado de un blog hecho con Wordpress. Si se ve mal, dejame un comentario y lo arreglo.

En la lista de PyAr preguntaron si había alguna forma de aplicar un decorador a todos las funciones de un módulo. Envié una solución sin probarla, que al verla unos días más tarde parece bastante buena :)

La comento aquí con un ejemplo. modulo.py contiene definiciones de funciones:

def a():
pass

def b():

print 42

def c():

a()

b()</pre>

y decoradores.py un decorador que imprime el nombre de la función llamada:

def nombrador(f):

    def inner(*a, **kw):

        print "Ejecutando %s" % f.__name__

        return f(*a, **kw)

    return inner

(Si no sabés lo que es un decorador, podés leer mi post Decoradores en Python I: Introducción)

En lugar de modificar las definiciones de funciones en modulo.py para aplicar el decorador a cada una de las funciones, ya sea usando el azúcar sintáctica de Python:

@nombrador

def a():

    ...

o mediante una llamada a la función:

a = nombrador(a)

podemos agregar el siguiente código al final de modulo.py:

for n,v in locals().items():

   if inspect.isfunction(v) and n != 'nombrador':

       locals()[n] = nombrador(v)

Vamos a explicarlo:

la llamada a la función built-in locals retorna un diccionario representando el espacio de nombres local: cada clave es un string representando el nombre de un objeto y cada valor es el objeto en si. Iteramos sobre la lista de pares (key, value) del mencionado dict y por cada uno verificamos si:

a) es una función (inspect.isfunction es apropiado para esto)

b) el nombre no es el del decorador que queremos aplicar (para no aplicar el decorador sobre si mismo!)

Si las condiciones a y b se cumplen, podemos guardar en el diccionario del espacio de nombres, bajo el nombre de la función que cumplió las condiciones, una versión decorada de la misma.

Agregamos algo más de código a modulo.py para que se llame a las funciones cuando lo ejecutemos:

if __name__ == '__main__':

    a()

    b()

    c()

Esta es la salida obtenida:

juanjo@fenix:~/python/muchosdecos$ python modulo.py

Ejecutando a

Ejecutando b

42

Ejecutando c

Ejecutando a

Ejecutando b

42

¿Querés probarlo? Bajá muchos.zip

Nota: para acceder a locals() no se puede utilizar iteritems por que el diccionario cambia durante la ejecución.

Comentarios

Comments powered by Disqus