Quantcast
Channel: Take it easy
Viewing all articles
Browse latest Browse all 19

El diablo está en los detalles, Python edition (y II)

$
0
0

En mi anterior post (y con “anterior” quiero decir “de hace más de un mes”, la frecuencia de mis entradas va a quedarse lejos de ser legendaria…) estuve comentando algunas características de Python que me parecían especialmente útiles. Herramientas como las comprensiones de listas y los generadores multiplican la productividad y hacen que el lenguaje sea compacto y legible. Un 2×1, como en los anuncios de detergente. Pero se me fue un poco de largo, y dejé fuera del mismo varias características igual de interesantes que quería explicar también. Si os parece bien, hoy echaremos un ojo a alguna más. Al final sólo a una más: ¡los decoradores!

Decoradores

Si vienes de Lisp y amigos y te hablo de metaprogramación en Python posiblemente te rías un rato, y tendrás razón. Python (o Ruby, o cualquiera de los demás lenguajes dinámicos que alardean de su capacidad de introspección) palidecen frente a las macros de Lisp en todo lo tocante a la metaprogramación, esto es así. Si el palabro os suena un poco raro, el concepto es sencillo: la metaprogramación es la manipulación y modificación de código por otro código. Literalmente, es un programa programando, un programa que se modifica a sí mismo u a otros programas, vamos.

En Lisp es un concepto muy natural: el código Lisp es una estructura de datos de Lisp válida (expresiones S, para los curiosos). Por tanto un programa (o fracción del mismo) en Lisp es un valor de entrada válido para una función en Lisp, y puede ser manejado como una estructura de datos más. Es apasionante, pero no es el caso de Python, claro, donde el código no se expresa como una estructura de datos del lenguaje. Aún así Python ofrece cierto grado de metaprogramación. Ciertas construcciones del lenguaje permiten modificar porciones de código existente, y de todas ellas los decoradores son sin duda las más amigables y usadas.

Los decoradores son funciones que modifican funciones (en realidad también pueden decorarse clases, pero centrémonos en las funciones por ahora). De hecho un decorador no es más que azúcar sintáctico para pasar una función como parámetro a otra función y obtener una tercera función como resultado. Toma ya, y ahora a ver cómo aclaro esto un poco (para la gente de mi edad que veía Super3 hace años, “la força del superguerrer que ha superat la força del superguerrer que…”). Creo que la mejor manera de explicar un decorador es ilustrándolo con un ejemplo, y de golpe todo tendrá mucho más sentido. Veamos primero que pinta tiene, y cual es el su función. Luego entramos en harina y miramos las tripas.

Un ejemplo muy ilustrativo son los decoradores de autorización de Django. Cuando se desarrolla una aplicación web, es bastante estándar el quere proteger ciertas características con autenticación. O sea, o el usuario está validado en el sistema y dispone de ciertos permisos, o no puede acceder a la característica. Django ofrece mecanismos para hacer esas comprobaciones de manera genérica y reusable. Para mi ejemplo voy a implementar un decorador (muy chorra él) que emule ese funcionamiento. Si el usuario no está validado (en nuestro ejemplo el usuario es nulo) no queremos que se ejecute nuestra función, sino darle un mensaje de error al usuario. Al turrón:

@is_authenticated    # esta es una función ya definida, luego la vemos
def my_secret_feature(user):
    print 'pasa pa dentro %s' % user

# usuario autenticado, debería poder entrar
user = 'aitor'
my_secret_feature(user)
# resultado -> pasa pa dentro aitor

# usuario no autenticado, debería recibir error
user = None
my_secret_feature(user)
# resultado -> sin pendiente no entras

Bien, el tema es el siguiente. Mi función my_secret_feature recibe un usuario como parámetro. Si el usuario es cualquier cosa menos nulo (None en Python), la función le da la bienvenida. Si el usuario es nulo, se pinta un mensaje de error (¡un rechazo en toda regla!) y no se ejecuta el cuerpo de la función. ¿Cómo es eso posible si la función no hace ninguna comprobación? Pues la magia está en ese @is_authenticated que precede a la definición de my_secret_feature. Ese es el decorador.

La sintaxis @ + nombre de función crea un decorador, y al aplicarse a una función (simplemente precediendo a su definición) lo que hace el intérprete es “envolver” la función con la función decoradora. Ese envolvimiento consiste en pasar a la función decoradora la función decorada como parámetro, y la primera ha de devolver una nueva función, que es la que realmente estamos invocando. Esa nueva función puede hacer lo que le plazca, teniendo acceso a la función original y sus parámetros. De manera más visual, estas dos sintaxis son equivalentes:

@is_authenticated
def my_function(arg):
    # cuerpo de la función

my_function('foo')

 

def my_function(arg):
    # cuerpo de la función

new_func = is_authenticated(my_function)
new_func('foo')

¿Se entiende el asunto? Lo que hace el @ es llamar a is_authenticated con my_function como parámetro, y retornar una nueva función (en la versión explícita new_func).  A partir de ese momento, cualquier invocación de my_function es en realidad una invocación de new_func. El decorador hace todo ese proceso de funciones que toman funciones como parámatros y devuelven funciones invisible, facilitando mucho el uso (y sobretodo la reutilización) de esos modificadores. ¿Y que pinta tiene una función decoradora? Pues echemos un ojo a la definición de is_authenticated:

def is_authenticated(func):
    def new_func(user):
        if user is None:
            print 'sin pendiente no entras'
            return
        return func(user)
    return new_func

Explicado rápidamente:

  • is_authenticated toma una función como parámetro
  • define una nueva función (en Python pueden definirse funciones anidadas, vamos, dentro de otras funciones) new_func que acepta un parámetro user
  • la lógica de new_func es:
    • si el parámetro user es nulo, se imprime el mensaje y se sale sin hacer nada más
    • en caso contrario, se llama a la función que is_authenticated ha recibido como parámetro pasando el parámetro user a la misma
  • is_authenticated retorna new_func, que a partir de entonces será la que invoquemos realmente

Volviendo a nuestro ejemplo inicial, lo que está pasando es que is_authenticated nos retorna new_func, aunque para nosotros se sigue llamando my_secret_feature. Al invocarla pasando user como parámetro, new_func comprueba si user es nulo. De serlo, pinta el error y retorna, jamás se invoca my_secret_feature original. De no serlo, se invoca my_secret_feature (es la func que ha recibido is_authenticated durante la decoración) pasándole el mismo user que hemos pasado a new_func. Y esa es la magia.

En fin, esto es sólo una parte de la potencia de los decoradores. Se pueden implementar decoradores como clases y no como funciones, lo que permite organizar mejor el código de decoradores complejos. Los decoradores pueden recibir sus propios parámetros, además de los de la función decorada, haciéndolos muy flexibles (un ejemplo que me gusta mucho es el decorador de caching de Django, dónde explicitas por cuántos segundos es válida la caché con @cache(num_segundos)). Y las clases también pueden ser decoradas (en vez de modificar una función, modificamos una clase). No es metaprogramación al nivel de Lisp, pero cambiar el comportamiento por defecto de ciertas porciones de código es muy fácil y elegante gracias a los decoradores.

Volvía a haber más, pero…

Soy un planificador de posts horrendo. Tenía intención de tocar al menos dos temas, pero me he liado con los decoradores y me he ido a las 1200 palabras en un santiamén. Como la idea es no aburrir (y seguir teniendo tema para escribir más posts, que tengo pocas ideas) vamos a dejarlo por hoy. Si tenéis dudas, aportaciones, correcciones, ahí están los comentarios. Si queréis que siga la “saga”, insistid también, así me obligo a darle un rato a la tecla. Y si queréis proponer tema para otro post, yo encantado, nunca sé sobre qué charlar un rato.

Muchas gracias por leer, como siempre. Y echadle un ojo Lisp, venga ;)



Viewing all articles
Browse latest Browse all 19

Trending Articles