Febrero 6

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()

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.