Sie sind auf Seite 1von 4

053-056_Python

06.02.2006

14:59

Uhr

Pgina

53

Python DESARROLLO

La serpiente que se muerde la cola.

META-PYTHON
Con la llegada de Ruby On Rails los programadores estn redescubriendo un concepto no demasiado moderno, pero sorprendente programas que modifican programas?. POR JOS M RUIZ

os lenguajes dinmicos (donde el tipo de variable se define en tiempo de ejecucin) siempre han sido degradados a lenguajes de segunda clase. Esto se debe a que se suelen emplear para resolver problemas rpidamente de manera sucia, ya que siempre se podr programar en algo serio como C, C++ o Java. Pero esto no es del todo cierto. Los lenguajes dinmicos poseen ciertas ventajas sobre los estticos, ventajas que los estticos estn constantemente intentando imitar.

Meta qu?
Una de estas ventajas es la metaprogramacin. Es una palabra extraa, as que es conveniente comenzar explicando un poco su significado. Meta podramos traducirlo como a nivel superior. Un metalenguaje es un lenguaje para hablar de un lenguaje. El metalenguaje est a un nivel superior respecto al lenguaje que describe. Si digo la palabra coche es un sustantivo estoy empleando el castellano para hablar del castellano y en la palabra car es un sustantivo empleo el castellano para hablar del ingls. Por tanto el castellano es el metalenguaje en ambos casos. La metaprogramacin consiste en programas que pueden modificarse a s mismos o a otros programas. Dicho de esta manera puede resultar lioso y complicado. Pero imaginemos por un momento que fuese posible

cambiar elementos del programa en tiempo real, no datos, sino el programa en s. Podemos imaginarnos un objeto que no hable cierto protocolo (por protocolo nos referimos a un conjunto de mtodos que permiten interactuar con el objeto, por ejemplo para guardarlo en disco duro). En determinado momento surge la necesidad de que ese objeto hable ese protocolo, pero el diseador del programa no tuvo en cuenta esa circunstancia, as que puede resolverlo envolviendo el objeto dentro de otro objeto o heredarlo de una superclase que s responda a ese protocolo. Ahora bien, si esta circunstancia se repite con muchos protocolos, entonces la jerarqua de herencia de las clases comienza a complicarse innecesariamente con cdigo repetido en muchas ramas. C++ intenta acercarse a una solucin mediante el uso de los Templates. Java por su parte provee al programador de la reflexin (los objetos pueden hacer preguntas a otros objetos) y, desde Java 1.5, las clases genricas, algo as como los Templates de C++. En la denominada jerarqua o extensin horizontal no heredamos sino que hacemos uso de herramientas que estn a los lados, no arriba como las superclases. Por tanto este tipo de extensiones no requieren aumentar la jerarqua de objetos con nuevas clases. An as existe otra posibilidad y si pudisemos aadir mtodos en caliente a nuestros objetos? De esa manera cuando un objeto necesite hablar cierto protocolo simplemente se aade a s mismo los mtodos necesarios, digamos que puede DECORARSE como los rboles de Navidad, donde vamos colgando bolas y adornos a nuestro antojo.

Conceptos bsicos
En los mticos laboratorios de Palo Alto de Xerox, Gregor Kiczales y otros desarrollaron a mediados de los 90 el concepto MOP, Meta Object Protocol. La idea es la de definir toda la maquinaria necesaria para trabajar en objetos en dos niveles. En el primero, el Metanivel, se usan las primitivas del lenguaje para crear objetos. En el segundo se usa esa maquinaria (crear clases, instancias, mtodos) para volver a definirlo todo de nuevo!. Cul es el objetivo? Si una clase est definida usando clases entonces podr modificarla, puesto que no es ms que una estructura de datos que est viva durante la ejecucin del programa. Esto permite cosas muy interesantes. Ver Figura 1.

Listado 1
01 02 03 04 05 06 07 08 09 10 11 12 >>> >>> ... ... >>> ... ... >>> >>> >>> 1 >>> class Clase1: def __init__(self,valor): self.valor = valor def imprime(self): print self.valor objeto = Clase1("1") objeto.imprime = imprime objeto.imprime(objeto)

Qu ha pasado aqu? Hemos definido una clase y una funcin, hemos creado una instancia de la clase y hemos aadido la funcin a la instancia de la clase!. Que ocurre si creamos un segundo objeto?
>>> objeto2 = Clase1("2") >>> objeto2.imprime(objeto2) Traceback (most recent callU

WWW.LINUX- MAGAZINE.ES

Nmero 15

53

053-056_Python

06.02.2006

14:59

Uhr

Pgina

54

DESARROLLO Python

last): File "<stdin>", line 1, in ? AttributeError: Clase1 instanceU has no attribute 'imprime' >>>

objeto2 no tiene imprime! Luego la modificacin a objeto slo le afectaba a l mismo y no al resto de instancias de Clase1. Y cmo es posible asignar cosas a una instancia? Pues porque Python representa las instancias como diccionarios, las asignaciones:
objeto.imprime = imprime objeto['imprime'] = imprime

Todas estas caractersticas nos abren un amplio abanico de posibilidades. Podemos modificar jerarquas de objetos en tiempo de ejecucin para que puedan adaptarse a nuevos protocolos. No vamos a prestar demasiada atencin a las metaclases en s, en un prximo artculo las veremos, sino a un patrn de diseo que nos permitir aadir funcionalidad de manera controlada, hablamos del patrn Decorator.

Entorno de una funcin


Para las sucesivas secciones es importante entender cmo funcionan el paso de parmetros en Python. En Python los parmetros de una funcin son en realidad una lista. De hecho Python recoge los parmetros y genera una lista con ellos. Esto se indica en la cabecera de la funcin mediante un * delante del nombre de la lista que guarda los parmetros. Una funcin acepta como segundo parmetro un diccionario, indicado mediante la aparicin de ** para los argumentos con nombre. De esta manera cualquier funcin en realidad acepta los parmetros:
funcion(*args, **kwdargs)

son equivalentes. Adems, las funciones se comportan en Python de igual forma que los datos, se pueden asignar a variables y pasar como parmetros. Podemos modificar el comportamiento de las instancias, pero qu ocurre con las clases?
>>> Clase1.imprime = imprime >>> objeto3 = Clase1("Hola") >>> objeto3.imprime() Hola >>>

Su nombre viene de que podemos verlo como un algoritmo que en el rbol de cdigo va aadiendo nuevo cdigo de manera automtica. Es algo parecido a lo que ocurre cuando en una pgina web vemos siempre el mismo men en la misma posicin a pesar de que cambiemos de una seccin a otra. El decorado envuelve el contenido de la pgina. El mundo de Python ha vivido una especie de guerra entre distintos grupos de sus desarrolladores por hallar la mejor sintaxis para este patrn. La comunidad Python es casi obsesiva respecto a su sintaxis, quieren que sea clara y concisa. Al final se impuso la siguiente sintaxis:
@decorator def funcion(): loquesea

@decorator es el nombre del Decorator que vamos a emplear. Tiene que llevar siempre delante una arroba y debe estar seguido de la funcin a decorar. Visto as no parece muy til, pero imaginemos una funcin, digamos VisitaWeb, a la que aadimos el siguiente Decorator:
@guardar_log def VisitaWeb(): ...

Pues tambin se pueden modificar en caliente. No slo eso, sino que a partir de ese momento cualquier instancia tendr acceso a esas modificaciones. Qu ocurre si hemos declarado la instancia antes de la modificacin y aadimos una funcin a la clase despus? (ver

Se suelen usar los nombres *args y kwdargs para nombrarlos. Es importante saber esto para poder crear funciones sin saber qu parmetros aceptarn, puesto que al final todas las funciones aceptan esos dos parmetros en realidad.

Listado 2
01 >>> class Clase2: 02 ... def __init__(self,valor): 03 ... self.valor = valor 04 ... 05 >>> objeto4 = Clase2("Hola")>>> def imprime(self): 06 ... print self.valor 07 ... 08 >>> Clase2.imprime = imprime 09 >>> objeto4.imprime() 10 Hola 11 >>>

Decorator
Qu es un Decorator? Pues es un patrn de diseo del famoso libro Design Patterns. Se gan su fama debido a que dio nombre a numerosas tcnicas ya establecidas. Las reuni y cre el concepto de patrn de diseo: un conjunto de objetos y comportamientos definidos que surgen con cierta facilidad en el desarrollo de cualquier sistema medianamente complejo. En el mundo de la programacin orientada a objetos son famosos muchos de estos patrones de diseo, el Observer o el Proxy son ejemplos de ello. El patrn Decorator es menos conocido porque realiza la tarea algo complicada de entender, y por tanto no todo el mundo se atreve a trabajar con l. Decorator modifica funciones. Digamos que recorre nuestro cdigo e inserta comportamiento adicional en l.

Guardara en un log la informacin de cada visita, con cada ejecucin de VisitaWeb(), y esto sin tener que aadir nada nuevo a VisitaWeb y pudiendo emplear en otros lugares @guardar_log. Por ejemplo en CompraEfectuada() o VisitaPeligrosa(). Podemos aadir funcionalidad a cdigo ya existente sin modificar nada, simplemente acompaando el cdigo de una lnea que indique qu Decorator aplicar. Pero no acaba ah la cosa, podemos anidar Decorators:
@comprueba_acceso @guardar_log def VisitaWeb(): ...

Figura 2) La modificacin de la clase afecta a todas sus instancias, pasadas o futuras.

Ahora bien, qu clase de magia negra o vud se esconde detrs de algo tan til? por qu no nos lo ensean en el segundo prrafo de cualquier tutorial de Python? Pues porque hasta hace poco era extremadamente complejo aadir

54

Nmero 15

WWW.LINUX- MAGAZINE.ES

053-056_Python

06.02.2006

14:59

Uhr

Pgina

55

Python DESARROLLO

Decorators en Python, y a pesar de las mejoras sigue siendo un proceso complejo.

Listado 4: Ejemplo de Decorator


01 02 03 04 05 06 07 >>> suma(1,2) ==> Ejecutando suma Resultado: 3 >>> funcion() ==> Ejecutando funcion Hola soy la funcin >>>

Crear un Decorator
Un Decorator es una funcin que acepta como parmetro una funcin y devuelve como resultado una funcin. Un poco lioso no? Desde luego que lo es, as que comencemos con un ejemplo sencillo. Queremos una Decorator que escriba por pantalla el nombre de la funcin sobre la que se aplique cada vez que sta se ejecute. Un Decorator no deja de ser una funcin Python como cualquier otra, veamos su definicin y resultado en la Figura 3. escribeNombre acepta una funcin y

Programacin avanzada
Ahora vamos a ver una de las construcciones ms potentes que puede tener un lenguaje de programacin. Es anterior a los Objetos y an as, a veces es mucho ms simple y potente. De hecho ya hemos hecho uso de ella. Como dira un periodista: dnde?, cundo?, por qu? y quin? Fijmonos de nuevo en la Figura 3. Hay algo raro ah? No es fcil darse cuenta, y eso que son 5 lneas de cdigo! La gran pregunta es qu ocurre con la f en decorator? Nada? Repasemos lo que sabemos de las variables. A pesar del hecho de que una funcin pueda ir en una variable extrae a algn lector, es algo que los programadores de C y C++ acostumbran a hacer mediante el uso de punteros a funciones. f es una variable que referencia a una funcin. Visto as no es nada especial, lo raro es que f es el parmetro que recibe escribeNombre, se crea la funcin decorator y salimos. Pero existen varias decorator annimas, al menos una por suma y otra por funcion. En ese momento f en otros lenguajes de programacin no apuntara a nada, estara en el limbo de las variables. Veamos un ejemplo para entenderlo. En la Figura 5 definimos un decorator al que hemos llamado de manera extremadamente original: cierre. cierre genera una funcin que hace uso del parmetro que le pasamos, el parmetro valor. Cuando la funcin hace uso de

valor captura su contenido!. A partir de ese momento esa funcin har uso del contenido que poseyera valor durante su definicin. Es raro? La verdad que en un primer momento si, a no ser que el lector tenga conocimientos de Lisp o Scheme ;). No en vano Peter Norvig, ver Referencia [1], dice que Python es Lisp con otra sintaxis. Pero cuidado, no creamos que lo que ocurre es que valor se convierte en una constante. No, sino que se CIERRA un entorno alrededor de cierre. Es como si junto a cierre fuese una variable llamada valor a la que cierre puede hacer referencia como si fuese global, pero slo para l mismo!.

Listado 3: Decorator escribeNombre


01 def escribeNombre (f): 02 def decorator (*args, **kwds): 03 print "==> Ejecutando ", f.func_name 04 f(*args, **kwds) 05 06 return decorator 07 08 @escribeNombre 09 def funcion(): 10 print "Hola soy la funcin" 11 12 @escribeNombre 13 def suma(a,b): 14 print "Resultado: ", a+b

Listado 6: Prueba de cierre


01 02 03 04 05 06 07 >>> >>> >>> 4 >>> 9 >>> a = cierre(3) b = cierre(8) a(1) b(1)

En la Figura 6 podemos ver un ejemplo de interaccin con cierre.Es importante tomar buena nota de los cierres, puesto que nos permiten generar funciones que guardan valores. Y esto es vital para los Decorators.

Dos Decorators mejor que uno


Para finalizar vamos a hacer algo til con esta tcnica. Generalmente se habla de los Decorators en trminos de servicios que nos gustara aadir al cdigo. Los ms tpicos son crear registros de uso, de seguridad, sistemas de cacheo o persistencia. Tambin se hace mucho hincapi en los multimtodos. Estos ltimos consisten en algo parecido a la sobrecarga de C++ o Java, esto es, poder definir muchas funciones y que se ejecuten en base a los tipos o cantidad de parmetros, pero que todas tengan el mismo nombre. Un ejemplo podra ser la funcin suma(a,b). No es lo mismo sumar enteros que flotantes, quebrados o nmeros complejos. En realidad cada tipo de nmero requiere su propia suma. Nosotros vamos a crear dos servicios de ejemplo. Uno ser para facilitar el debug-

crea otra que hemos dado en llamar decorator. El nombre de esta ltima funcin no importa porque se perder, as que podemos usar siempre el mismo nombre para distinguirla. El objetivo de esta funcin es ser devuelta y asignada en lugar de la funcin original. La funcin interna decorator aade el cdigo u operaciones que sean necesarias y ejecuta en algn momento (o no, depende de lo que queramos) la funcin original. Como podemos observar en la ejecucin de la Figura 4, ahora, cada vez que se ejecute funcion o suma se imprime un mensaje en pantalla. Si queremos eliminar ese comportamiento slo tenemos que quitar @escribeNombre de la definicin de la funcin en cuestin.

Listado 5: Funcin cierre


01 def cierre (valor): 02 03 def incrementa (cantidad): 04 return cantidad + valor 05 06 return incrementa

WWW.LINUX- MAGAZINE.ES

Nmero 15

55

053-056_Python

06.02.2006

14:59

Uhr

Pgina

56

DESARROLLO Python

Listado 7: Decorators debug y cachea


01 def debug(f): 02 03 04 05 06 07 def envoltura(*args, **kwargs): resultado = f(*args, **kwargs) print "Llamando a", nombre," con ", args, kwargs, " y devuelve ", repr(resultado) 08 09 10 11 12 def cachea(f): 13 14 15 16 17 18 19 20 21 22 23 24 cache[llave_1,llave_2] = f(*args,**kwargs) 25 26 27 28 29 30 31 class Calculadora: 32 33 34 35 36 37 38 39 40 41 + valor 42 return resultado @debug @cachea def suma(self, valor): resultado = self.valor def getValor (self): return self.valor def __init__ (self, valor): self.valor = valor return envoltura return resultado resultado = cache[llave_1,llave_2] # Cierre cache = {} def envoltura(*args, **kwargs): llave_1 = hash(args) llave_2 = hash(tuple(kwargs.iteritems())) try: resultado = cache[llave_1,llave_2] print "****CACHE****" except KeyError: return envoltura return resultado nombre = f.func_name

ging de un programa, el otro ser un cacheador de valores. El cacheador hay que explicarlo un poco. Imaginemos una situacin donde necesitamos calcular un valor muy complejo una y otra vez, pero que el clculo sea siempre el mismo y devuelva siempre el mismo valor para los parmetros dados. O, mirando a la Web, imaginemos que generamos la misma pgina y que slo la actualizamos cada cierto tiempo. Hacer algo as requiere mucha maquinaria, tenemos que capturar la peticin, determinar si los parmetros para el clculo o pgina web han variado o se da cierta circunstancia (han pasado 5 minutos desde la ltima vez que se ha invocado a la funcin?) y entonces devolver el valor o calcularlo de nuevo. Necesitamos al menos un objeto y lo que es peor: cada funcin que requiera de esta propiedad deber implementarla de nuevo o heredar de un objeto que la implemente. Pero en Python no tenemos herencia mltiple, as que acabaremos con dos versiones de cada objeto: Pagina y CacheaPagina, Clculo y CacheaClculo. Los Decorators solucionan este problema de una forma genrica y muy elegante. Fijmonos en la Figura 7. Se definen dos Decorators: cachea y debug. debug es simple, se parece mucho al ltimo ejemplo, simplemente imprime informacin de la funcin (su nombre, parmetros y resultado) con cada ejecucin de la misma. cachea realiza ms trabajo. La clave es la variable cache que es un diccionario donde almacenamos los parmetros que se pasan a la funcin en cuestin. cache est dentro del cierre que creamos con el Decorator. Cuando recibimos los parmetros intentamos hallar el valor de la funcin en el diccionario cache. Si no aparecen, entonces tenemos que calcularlos y almacenarlos cache usando los parmetros como llave. Pero si aparecen, simplemente devolvemos el valor asociado. De esta manera podemos cachear resultados. La mayor complicacin, aparte de todo lo que hemos estado hablando en este captulo, se debe a que los diccionarios no pueden ser usados para generar un hash,

y kwargs es un diccionario. Si un objeto no se puede reducir a un hash, entonces no puede ser usado como llave en un diccionario. Por eso hemos de generar una tupla a partir de kwargs, y a partir de ella generar un hash. Por seguir esa dinmica hacemos lo mismo con args, aunque no sera necesario. Cuando se intenta acceder a un valor en un diccionario, y ese valor no existe, el diccionario arroja una excepcin KeyError (error en la llave pasada). As que intentamos el acceso dentro de una estructura try y capturamos esa excepcin, que nos indicar cundo tenemos que calcular el valor. Como ejemplo, en la clase Calculadora hacemos uso de los dos Decorators, de manera que no slo cacheamos, sino que adems mostramos informacin de debug para la funcin sobre la que los apliquemos.

Decorando proyectos
El concepto de los Decorators es parecido al de la ahora famosa Aspect Oriented Programming (AOP). Conforme avanzan los aos, los estudiosos se van dando cuenta de que la Programacin Orientada a Objetos no es la mejor solucin a todos los problemas y puede llegar a resultar engorrosa. Los Decorators no son ms que otro de los mecanismos de extensin horizontal que estn comenzando a hacerse hueco en la caja de herramientas de los mejores diseadores. Y por eso Python los ha incorporado ;)I

RECURSOS
[1] http://www.norvig.com

Jos Mara Ruiz actualmente est realizando el Proyecto Fin de Carrera de Ingeniera Tcnica en Informtica de Sistemas. Lleva 8 aos usando y desarrollando software libre y, desde hace dos, se est especializando en FreeBSD.

56

Nmero 15

WWW.LINUX- MAGAZINE.ES

EL AUTOR

Das könnte Ihnen auch gefallen