Decoradores en Python (I)


Este artículo es el primero de un plan de 3 artículos. Empezamos con una introducción a los decoradores en Python.

Funciones

Cómo todo en Python, las funciones son objetos. La forma más común de crear un objeto de tipo <function> es mediante el keyword def:

def saludo():
    print "Hola"

Al realizar esta definición, el cuerpo de la función es compilado pero no ejecutado y el objeto de tipo <function> es asociado al nombre ’saludo’. Mediante este nombre podemos referirnos al objeto:

saludo
<function saludo at 0xb7d82fb4>

y utilizando la notación de paréntesis podemos llamar a (ejecutar) la función.

saludo()
Hola

Una función puede tener parámetros (los parámetros son nombres a los que podemos referirnos en el cuerpo de la función):

def saludo2(nombre):
    print "Hola %s" % nombre
def saludo3(nombre, apellido):
    print "Hola %s %s" % (nombre, apellido)

Cuando llamamos a la función con argumentos (los argumentos son valores que en principio se asocian uno a uno a los parámetros de la función):

saludo2("Ceci")
Hola Ceci
saludo3("Ceci", "Pucci")
Hola Ceci Pucci

Los últimos n parámetros pueden tener valores por defecto, entonces estas definiciones y sus consiguientes ejecuciones son válidas:

def saludo4(nombre, apellido="Conti"):
    print "Hola %s %s" % (nombre, apellido)
saludo4("Juanjo")
Hola Juanjo Conti
saludo4("Juanjo", "Garau")
Hola Juanjo Garau
def saludo5(nombre="Juanjo", apellido="Conti"):
    print "Hola %s %s" % (nombre, apellido)
saludo5()
Hola Juanjo Conti
saludo5("Mary")
Hola Mary Conti

Los últimos n argumentos pueden ser argumentos nombrados, es decir utilizando el nombre de los parámetros con los que el argumento se debe asociar. En las siguientes ejecuciones se pueden ver ejemplos de esto:

def saludo6(tratamiento, nombre, apellido):
    print "Hola %s %s %s" % (tratamiento, nombre, apellido)
saludo6("Sr.", apellido="Conti", nombre="Juanjo")
Hola Sr. Juanjo Conti
saludo6("Sr.", "Juanjo", apellido="Conti")
Hola Sr. Juanjo Conti

Los parámetros de una función pueden terminar con *<nombre> (una tupla con los últimos argumentos posicionales) y/o **<nombre> (un diccionario con los últimos argumentos nombrados).

def saludo7(tratamiento, *args):
    print "Hola %s %s" % (tratamiento, " ".join(args))
saludo7("Sr.", "Juanjo", "Conti")
Hola Sr. Juanjo Conti
saludo7("Sr.", "Juanjo", "Conti", "Garau")
Hola Sr. Juanjo Conti Garau

Notemos que esta forma de definir una función es bastante útil cuando no sabemos el número de argumentos que se recibirán.
La siguiente es la forma más genérica de definir una función:

def saludo8(*args, **kwargs):
    pass

Decoradores

Un decorador es una función ‘d’ que recibe como argumento otra función ‘a’ y retorna una nueva función ‘b’. La nueva función ‘b’ es la función ‘a’ decorada con ‘d’.

Supongamos que queremos avisarle a un sistema de seguridad cada vez que se ejecutan las funciones abrir_puerta y cerrar_puerta. Para hacer una simplificación, el aviso simplemente será imprimir por un mensaje en la pantalla. Podemos escribir el siguiente ‘decorador’:

def avisar(f):
    def inner(*args, **kwargs):
        f(*args, **kwargs)
        print "Se ha ejecutado %s" % f.__name__
    return inner

Las siguientes son las funciones a decorar:

def abrir_puerta():
    print "Abrir puerta"

def cerrar_puerta():
    print "Cerrar puerta"
abrir_puerta()
Abrir puerta

cerrar_puerta()
Cerrar puerta

Y ahora solo nos limitamos a seguir la definción que di al principio de un decorador:

abrir_puerta = avisar(abrir_puerta)
cerrar_puerta = avisar(cerrar_puerta)

Listo!, ambas funciones han sido decoradas:

abrir_puerta()
Abrir puerta
Se ha ejecutado abrir_puerta

cerrar_puerta()
Cerrar puerta
Se ha ejecutado cerrar_puerta

Azúca sintáctica

En Python 2.3, la anterior era la forma de decorar una función. A partir de Python 2.4 se a añadido azúcar sintáctica al lenguaje que nos permite hacer lo mismo de esta forma:

@avisar
def abrir_puerta():
    print "Abrir puerta"

@avisar
def cerrar_puerta():
    print "Cerrar puerta"

Esta es una forma mucho más visual de hacerlo.

Encadenando decoradores

La decoración de funciones puede encadenarse. Para ejemplificarlo vamos a suponer ahora que solo usuarios autenticados en el sistema pueden ejecutar las funciones abrir_puerta y cerrar puerta.

Nuevamente hacemos una simplificación. Existe la variable AUTHENTICATED que indica el estado del usuario actual. Si el usuario no está autenticado y se intente ejecutar alguna de las funciones, una excepción es lanzada.

def autenticado(f):
    def inner(*args, **kwargs):
        if AUTHENTICATED:
            f(*args, **kwargs)
       else:
           raise Exception
    return inner

Luego, la definición de abrir_puerta y cerrar_puerta debería ser:

@autenticado
@avisar
def abrir_puerta():
    print "Abrir puerta"

@autenticado
@avisar
def cerrar_puerta():
    print "Cerrar puerta"

Con AUTENTICATED = True:

cerrar_puerta()
Cerrar puerta

Se ha ejecutado cerrar_puerta

Pero si AUTHENTICATED = False:

cerrar_puerta()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in inner
Exception

4 Comments, Comment or Ping

  1. Gran articulo!
    Quedo a la espera de los proximos dos ;)
    Saludos

    Julio 11th, 2008

  2. Gonzalo

    Muy bueno. Lo marqué para tenerlo como ayudamemoria de decoradores :)

    Julio 11th, 2008

  3. Pedagógicamente, deberías explicar antes de llegar a decoradores que es un “closure”, sino metiendo la variable AUTENTICATED (que, btw, debería ser AUTHENTICATED) le complica la vida a la persona que está siguiendo esto.

    Después lo veo piola. Obvio, faltan cosas, pero asumo que por eso pusiste que esta es la primera parte, ;)

    Saludos!

    Julio 16th, 2008

  4. Juanjo

    Gracias Facu por la sugerencia y la corrección.

    Julio 17th, 2008

Reply to “Decoradores en Python (I)”