Decoradores en Python (I) - Introducción
Este post fue migrado de un blog hecho con Wordpress. Si se ve mal, dejame un comentario y lo arreglo.
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 AUTHENTICATED = 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
update: 2° entrega.
Comentarios
Comments powered by Disqus