You are on page 1of 84

DVD GRATIS

Debian 6

MANUAL PRCTICO
ENERO 2012
Pennsula y Baleares 6,95
Canarias 7,05

17

PROYECTOS COMPLETOS
Y FUNCIONALES

Especia

06 BSICO

Introduccin a
Python con
ejemplos prcticos

48 INFRAESTRUCTURAS

Explota los motores tras


Facebook, Google y Twitter

59 MDULOS

Descubre cmo programar


para 3D y a manipular datos
e imgenes

Aprende de desarrolladores
profesionales y de sus
ejemplos del mundo real

Automatiza formularios web


Integra Python con Java, .Net y Qt
Ampla Open/LibreOffice
Crea y manipula PDFs
Maneja grandes volmenes de datos

WWW.LINUX- MAGAZINE.ES

8 413042 594529

Tu Gua Prctica

00006

Denis Babenko - 123RF.com

En la tienda de Linux Magazine (www.linux-magazine.es/tienda) vendemos revistas y libros que pueden ser de inters a nuestros lectores. Recuerda tambin que con una subscripcin Digital o Club,
podrs acceder a las ofertas (www.linux-magazine.es/digital/ofertas) de Linux Magazine donde puedes conseguir software, gadgets, libros y servicios. Este mes en nuestra tienda...

Manual LPIC-1
El nico manual en castellano para la certificacin completa LPIC-1 (exmenes 101 y 102). Ms de 250 pginas
de ejemplos reales tomados de ambos exmenes explicados en todo detalle con ejercicios para prcticas y sus
soluciones.
Preparado para el nuevo programa que entra en vigor a
partir del 2009, aprobado y recomendado por LPI International y con la garanta de Linux Magazine.
La gua perfecta en castellano para preparar el examen para cualquier persona que tenga conocimientos de Linux.
Se cie muy bien a los objetivos del nivel 1 de LPI
(LPIC-1) actualizados en Abril de este ao, cosa que
es de agradecer.
Un avance muy importante en el desarrollo de los
programas de certificacin LPI en Espaa.

www.lpi.org.es
Consguelo en nuestra tienda.

DVD/EDITORIAL

Debian 6

Especial Python
Bautizado en honor al grupo de cmicos britnicos Monty
Python, Guido Von Rossum concibi este lenguaje en los
aos 80, comenzando su implementacin en 1989. Gracias a su sencillez y a la idea de que el cdigo bello se
consigue siendo explcito y simple, Python se ha
convertido en sus poco ms de 20 aos de existencia en el lenguaje favorito para scripts, aunque tambin para grandes infraestructuras y
aplicaciones.
As, la mayor parte de los servicios de Google y Facebook se construyeron utilizando
Python, las aplicaciones de diseo Inkscape
o Blender (entre muchas otras) utilizan un
motor Python para sus plugins. Tambin se
utiliza ampliamente en la investigacin en
entidades que van desde CERN hasta la
NASA.
Adems, su clara y bien pensada sintaxis,
su modularidad y su sobresaliente rendimiento a pesar de ser interpretado hacen de
Python un excelente lenguaje educativo, ideal
para ensear programacin del mundo real a todos
los niveles.
Con este especial pretendemos que el lector pueda
descubrir Python desde el principio, creando para ello
artculos que abordan desde tutoriales introductorios, hasta
programacin de alto nivel y para usos avanzados. Cada seccin
viene con varios ejemplos prcticos, cdigo y soluciones extradas del

mundo real.

DVD: Debian 6.0.3

Formato del Cdigo en este Especial


Aunque la norma en el sangrado de
cdigo en Python es que las tabulaciones
a principio de lnea sean mltiplos de cuatro espacios, para favorecer la legibilidad
de los listados en este especial, cada tabulacin se ha reducido a un solo espacio.
De esta manera, cdigo que normalmente se escribira como:
for i in range (1, 5):
if ((i % 2) == 0):
print i, es par
else:
print i, es impar

W W W. L I N U X - M A G A Z I N E . E S

Se reproduce as en los artculos:


for i in range (1, 5):
if ((i % 2) == 0):
print i, es par
else:
print i, es impar
A pesar de ser tcnicamente correcto,
animamos a que, si se transcribe cdigo
para su ejecucin, se utilice la convencin de los 4 espacios.
En todo caso, todo el cdigo est disponible, con su formato convencional, en el
sitio de Linux Magazine en [1].

En el DVD de este especial encontrars


la versin Live de la ltima iteracin
estable de Debian [2] para arquitecturas
de 32 bits (la ms universal). Al ser
Debian la ms completa de las distros
GNU/Linux, podrs encontrar en sus
repositorios todas las herramientas,
mdulos e infraestructuras que necesitars para seguir los artculos de este
especial.

Recursos
[1] Todo el cdigo de este especial: http://
www.linux-magazine.es/Magazine/
Downloads/Especiales/06_Python
[2] Debian: http://www.debian.org/index.
es.html

PYTHON

CONTENIDO

Python 01

Introduccin

Avanzado

Integracin

06 Primeros Pasos

19 Sin Nombre

33 Limpieza Total

Python es un lenguaje potente,


seguro, flexible pero sobre todo sencillo y rpido de aprender, que nos
permite crear todo lo que necesitamos
en nuestras aplicaciones de forma gil
y eficaz

Python es un lenguaje de programacin multiparadigma, y las funciones


lambda son parte fundamental de l,
aunque como veremos, existen buenas razones para no abusar de ellas.

AJAX es la palabra de moda, Google


usa AJAX, Yahoo usa AJAX todo el
mundo quiere usar AJAX pero lo
usas t? y ms importante an qu
demonios es AJAX?

Integracin
23 Python no hay ms que UNO

10 lbum Fotogrfico

Has visto alguna vez a los brokers de


bolsa y sus sofisticados y caros programas para ver las cotizaciones de
las empresas en bolsa en tiempo real?
Nosotros haremos lo mismo con
Python, OpenOffice y la tecnologa
UNO de OpenOffice.

Siguiendo con nuestro paseo por


Python, vemos caractersticas bsicas,
y concretamente en este artculo, el
tratamiento de ficheros creando un
programa para la ordenacin de colecciones de fotos.

39 De Serpientes y Primates
.NET est avanzando, y Python no se
ha quedado atrs. En lugar de combatirlo, ha entrado en simbiosis con ella.
Con Ironpython podremos hacer uso
de toda la potencia de .NET desde
nuestro lenguaje favorito.

15 Desparasitando Serpientes
Da igual lo buenos programadores
que seamos, tarde o temprano daremos con ese BUG que ser nuestro
peor enemigo. Veamos cmo podemos emplear herramientas para derrotarlo con mayor facilidad.

43 Desarrollo Rpido!

28 Cuando los Mundos Chocan

Otras Secciones
03
82

DVD Debian 6
Informacin de Contacto

PYTHON

Os descubrimos Jython, la forma mas


sencilla de desarrollar vuestras aplicaciones Java como si las programrais
con Python.

Ha llegado el cliente y te lo ha dejado


claro: necesita el programa para ayer.
Ha surgido un problema enorme y es
necesario resolverlo en tiempo rcord.
La desesperacin se palpa en el
ambiente y todos los ojos miran a tu
persona. Devuelves una mirada de
confianza y dices con tono tranquilo:
No te preocupes, tengo un arma
secreta para acabar con el problema.

W W W. L I N U X - M A G A Z I N E . E S

CONTENIDO

Python 01
VERS
IN
DVD
GNO
ME L
IVE

Infraesctructuras
48 Pyramid
Uno de los rivales de peso de Django
est creciendo en popularidad poco a
poco.

Libreras
65 Grficas 3D
Crear grficos 3D no es nada difcil en
Python... sobre todo si tenemos a
mano la librera VTK.

52 Guitarrazos
Los creadores del proyecto Django nos
hablan de la formacin de la Django
Software Foundation y mostramos
cmo comenzar con esta infraestructura web.

55 Seriales
Hacer que distintos servicios se comuniquen entre ellos es todo un problema que Facebook ha tratado de
solucionar con Thrift.

69 Vigilantes del planeta


Quin no ha querido alguna vez sentirse como esos informticos de la
NASA en su centro de control? Hoy
nos construiremos el nuestro y controlaremos el planeta y sus alrededores.

73 Enredados
Podemos automatizar comandos y
programas grficos, por qu no automatizar la interaccin con pginas
web? En este artculo crearemos un
pequeo script que puede ahorrarnos
mucho trabajo con el ratn.

77 ReportLab

Libreras
59 Cuaderno de Bitcora
Te acuerdas de cuando cambiaste la
versin de Firefox por ltima vez? y
de por qu instalaste ese programa tan
raro que parece no servir para nada ?
Yo tengo mala memoria, as que uso
un cuaderno de bitcora.

W W W. L I N U X - M A G A Z I N E . E S

Hoy en da se hace imprescindible disponer de herramientas que permitan


generar informes en PDF de alta calidad rpida y dinmicamente. Existen
diferentes herramientas para esta finalidad, entre ellas cabe destacar ReportLab, biblioteca gratuita que permite
crear documentos PDF empleando
como lenguaje de programacin
Python.

3
g.
p
n
e
cin
a
m
r
info
Ver

PYTHON

INTRODUCCIN

Primeros Pasos

Primeros Pasos

Anita Patterson - morguefile.com

Aprende a programar con este lenguaje de programacin multiplataforma

Python es un lenguaje potente, seguro, flexible pero sobre todo sencillo y rpido de
aprender, que nos permite crear todo lo que
necesitamos en nuestras aplicaciones de
forma gil y eficaz. Por Jos Mara Ruz

Para empezar, debemos saber por qu


Python es interesante, por qu es tan
famoso y cada vez ms utilizado. Mirando
un poco por Internet se pueden encontrar
multitud de aplicaciones que nos muestran
parte de las capacidades de este lenguaje
de alto nivel. Vamos a enumerar algunas
de sus sorprendentes caractersticas:
Orientado a objetos Esto no significa
que sea exclusivamente orientado a
objetos, podemos utilizarlo como queramos, aunque le sacaremos ms provecho si usamos su implementacin de
OOP (Programacin Orientada a Objetos).
Libre y gratuito Desde la red, podemos descargar el interprete y su cdigo
fuente, y al ser un lenguaje de script,
viene con la mayora de las distros
GNU/Linux de manera predeterminada,
siendo posible ver el cdigo de una
enorme parte del software desarrollado
para Python.
Portable Al ser interpretado, podemos
ejecutar nuestros programas en cualquier S.O. y/o arquitectura simplemente
teniendo instalado previamente el intrprete en nuestro ordenador .
Potente Realizar un programa bajo
este lenguaje seguramente nos costara
entre la mitad o la cuarta parte del
tiempo que tardaramos en desarrollar el
mismo programa en C/C++ o Java.
Claro Puede que sta sea una de las
caractersticas ms alabadas de Python:

PYTHON

Los programas escritos en este lenguaje


tienden a ser fciles de comprender y,
por tanto, de mantener por terceros. Una
enorme ventaja frente a lenguajes como
C o Perl.
Pero veamos una breve comparativa con
otros lenguajes:
Hola Mundo en C:
main ()
{
printf(Hola Mundo);
}

Hola Mundo en Java:


public static void U
main(String args[])
{
System.out.println(Hola U
Mundo);
}

Hola Mundo en Python:


print Hola Mundo

Aunque los Hola Mundo no son muy


indicativos de nada, ntese la ausencia de
puntos y comas, llaves, declaracin de funciones y otros trastos que entorpecen el
cdigo. Esto es incluso ms obvio en programas ms largos.
Python dispone de otras caractersticas
que lo convierten en el lenguaje favorito

de una comunidad de desarrolladores


cada vez ms amplia. Por ejemplo, permite la declaracin dinmica de variables, es decir, no tenemos que declarar
las variables ni tener en cuenta su
tamao, ya que son completamente dinmicas. Adems, dispone de un gestor de
memoria que, de manera similar al de
java, se encargar de liberar memoria de
objetos no utilizados. Sin embargo, y al
igual que Java, no permite usar la memoria a bajo nivel como C, con el que nos
podamos referir a zonas de memoria
directamente.
Adems se puede combinar con otros
mltiples lenguajes de programacin.
Podemos mezclar en nuestras aplicaciones
Python y Java (Jython ver el artculo al
respecto en la pgina 28 de este especial),
por ejemplo. O Python con C/C++, lo
cual hace que resulte mas potente si cabe.
Python tambin cuenta con una amplia
biblioteca de mdulos que, al estilo de las
bibliotecas en C, permiten un desarrollo
rpido y eficiente.
La sencillez de Python tambin ayuda a
que los programas escritos en este lenguaje
sean muy sintticos. Como podemos ver
en el ejemplo Hola Mundo anterior, la
simplicidad llega a ser asombrosa. Si este
programa ya supone ahorrarte 4 5 lneas
de cdigo, con una sintaxis tan sencilla y
ordenada podemos imaginar que un programa de 1000 lneas en Java, en Python se
redujeran unas 250.

W W W. L I N U X - M A G A Z I N E . E S

Primeros Pasos

Uso
Para empezar a matar el gusanillo, podemos ir haciendo algunas pruebas interesantes. Vayamos al intrprete Python. Para
ello, basta con escribir python en el
prompt de una terminal (por ejemplo,
Bash en GNU/Linux o Powershell en Windows) y probar nuestro Hola Mundo:
>>> print Hola Mundo
Hola Mundo

Ahora probemos a utilizar algunas variables:


>>> suma = 15 + 16
>>>
>>> print el resultado de la U
suma es: , suma
el resultado de la suma es: 31

Es recomendable trastear un poco con esto


antes de ponernos a programar algo ms
complicado, ya que de esta manera es ms
sencillo hacerse con la sintaxis mucho ms
rpidamente viendo los resultados de cada
prueba.
Veamos ahora alguna propiedad interesante de Python. Los ficheros en Python
no tienen por qu llevar extensin ninguna, pero seguramente querremos tenerlos diferenciados del resto de ficheros que
tengamos. Por ello se suele utilizar la
extensin .py.
Pero cmo sabe el sistema que intrprete utilizar cuando queramos ejecutar
nuestros scripts? Sencillo: Imaginemos que
tenemos un ejemplo.py, al ser un lenguaje
tipo script, debemos poner #! seguido de la
ruta del intrprete de Python en la cabecera del fichero. De esta manera, y dndole
permisos de ejecucin (chmod +x ejemplo.py en GNU/Linux), obtenemos un programa listo para su ejecucin.
Si nuestro intrprete Python se halla en
/usr/bin/, el contenido de ./ejemplo.py
quedara, pues, como sigue:

forma. En Python todo se hace de un solo


modo, de hecho es parte de su filosofa:
Solo Hay Una Manera de Hacer Las
Cosas.
Comencemos con lo ms simple en todo
lenguaje, la asignacin a una variable:
cantidad = 166.386

(Para los que no lo recuerden, 166,386 era


la cantidad de pesetas que hay en un euro).
Lo primero que hay que apreciar es que
no se usa el ;. Esto es una caracterstica de
las muchas que hacen a Python diferente.
Una sentencia acaba con el retorno de
carro, aunque el ; se puede usar cuando
dos sentencias estn en la misma lnea:

Despus de toda la introduccin tcnica va


siendo hora de que veamos cmo es el formato de los programas en Python. Esto es
importante porque, mientras la norma en
la mayora de los lenguajes es dejar al programador la decisin de la manera en que
deben ser formateados los archivos fuente,
en Python es obligatorio hacerlo de cierta

W W W. L I N U X - M A G A Z I N E . E S

while(<condicin>)

al igual que if
if (<condicin>)

Por qu no he puesto cuerpos de ejemplo


en esos bucles? Pues porque ahora viene
otra novedad. En Python no se usan las
famosas { y } para delimitirlas. Se decidi
(y no a todo el mundo le gusta) que se usara la posicin como delimitador. Esto, as,
suena algo extrao, pero si se ve es mucho
ms sencillo:
>>> cantidad = 2
>>> for i in range(1,10):
print cantidad*i

cant = 166.386; ptas = 3000

Como es un lenguaje dinmico, no hay


que declarar el tipo de las variables, pero
una vez que una variable ha sido definida
(lo que se hace asignndole un valor), esa
variable guarda su tipo y no lo cambiar a
lo largo de la ejecucin del programa.
Tambin tenemos a nuestra disposicin
los operadores habituales de otros lenguajes: +, -, *, /, etc. Y una vez que sabemos
cmo manejar operadores y asignaciones,
se pueden hacer cosas tiles, pero slo de
manera lineal. Para que la ejecucin no sea
lineal necesitamos los bucles y los condicionales.
El tema de los bucles en Python es algo
especial. Puede que estemos acostumbrados a los que son de tipo C:
for (a = 1; a < 10; a++) U
printf(%d\n,a);

Sin embargo, en Python se toma un enfoque funcional prestado de otros lenguajes


como Lisp o Haskell. Se utiliza una funcin especial llamada range. La funcin
range genera una lista de nmeros:
range(1,10)

#! /usr/bin/python
print Hola Mundo

INTRODUCCIN

generar una ristra de nmeros del 1 al 9.


De esta manera, podemos utilizar una funcin range para crear un bucle for en
Python de la siguiente manera:
for i in range(1,10)

Este bucle iterara con i desde 1 a 9, ambos


inclusive. La versin del bucle while en
Python es ms normal

WWW.LINUX- MAGAZINE.ES

Comencemos mirando a ese :. Los dos


puntos marcan el inicio de un bloque de
cdigo Python. Ese bloque aparecer en
bucles, funciones o mtodos. Si nos fijamos bien en la sangra de la siguiente
lnea, vemos una serie de espacios (logrados pulsando la tecla TABULADOR) y una
sentencia. Esos espacios son vitales, ya
que marcan la existencia de un bloque de
cdigo. Adems, son obligatorios.
Este es uno de los hechos ms controvertidos de Python, pero tambin mejora
mucho la legibilidad del cdigo. Slo existe
una manera de escribir Python, as que
todo el cdigo Python se parece y es ms
fcil de entender. El bloque acaba cuando
desaparecen esos espacios:
>>> for i in range(1,10):
... print cantidad*i
... cantidad = cantidad + 1
...
>>>

Funciones y Objetos
Ya tenemos las piezas fundamentales para
entender las funciones y los objetos. La
declaracin de una funcin en Python
tiene una sintaxis muy simple:
def nombre_funcion U
(<lista argumentos>):
<CUERPO>

Fcil no? Al igual que las variables, a los


argumentos no se les asignan tipos. Existen muchas posibilidades en los argumentos, pero los veremos ms tarde. De
momento examinemos un ejemplo simple:

PYTHON

INTRODUCCIN

Primeros Pasos

>>> def imprime (texto):


... print texto
>>> imprime(Hola mundo)
Hola mundo
>>>

Vuelve a ser sencillo. Y los objetos? Pues


tambin son bastante simples de implementar. Podemos ver un ejemplo en el Listado 1. Con class declaramos el nombre de
la clase, y los def de su interior son los
mtodos. El mtodo __init__ es el constructor, donde se asignan los valores iniciales a las variables. __init__ es un mtodo
estndar y predefinido, lo que quiere decir
que tendremos que usar se y no otro para
inicializar el objeto. Todos los mtodos,
aunque no acepten valores, poseen un
parmetro self. Este es otro punto controvertido en Python; self es obligatorio, pero
no se usa al invocar el mtodo. Cmo se
crea el objeto?
>>> a = Objeto(20)

Es como llamar a una funcin. A partir de


este momento a es una instancia de
Objeto, y podemos utilizar sus mtodos:
>>> print a.getCantidad()
20
>>> a.setCantidad(12)
>>> print a.getCantidad()
12

No hay que preocuparse por la administracin de la memoria del objeto ya que,


cuando a no apunte al objeto, el gestor de
memoria liberar su memoria.
Ya tenemos las bases para construir algo
interesante. Por supuesto, nos dejamos
infinidad de cosas en el tintero, pero siempre es mejor comenzar con un pequeo
conjunto de herramientas para empezar a
usarlas. En todo caso, tendremos tiempo
de profundizar en los siguientes artculos
de este especial.

Listado 1: Una Clase Sencilla


01 class Objeto:
02 def __init__ (self, cantidad):
03 self.cantidad = cantidad
04
05 def getCantidad(self):
06 return self.cantidad
07
08 def setCantidad(self,
cantidad):
09 self.cantidad = cantidad

PYTHON

Listado 2: Adicin y Eliminacin de Elementos de Lista


01 >>> b = [ 1 , 2 , 1 ]
02 >>> b.append(3)
03 >>> b.append(4)
04 >>> b
05 [1 , 2 , 1 , 3 , 4 ]
06 >>> b.remove(1)
07 >>> b
08 [2, 1, 3, 4]
09 >>> b.pop()
10 4
11 >>> b
12 [2, 1, 3]
13 >>> b.insert(1,57)
14 >>> b

Estructuras de Datos
Una de las razones por las que los programas scripts de Python resultan tan potentes, es que nos permiten manejar estructuras de datos muy verstiles de manera
muy sencilla. En Python estas estructuras
son las Listas y los Diccionarios (tambin
llamados Tablas Hash).
Las listas nos permiten almacenar una
cantidad ilimitada de elementos del mismo
tipo. Esto es algo inherente a casi todos los
programas, as que Python las incorpora
de fbrica. Las listas de Python tambin
vienen provistas de muchas ms opciones
que sus semejantes en otros lenguajes. Por
ejemplo, vamos a definir una lista que
guarde una serie de palabras:
>>> a = [Hola, Adios, U
Buenas Tardes]
>>> a
[Hola, Adios, U
Buenas Tardes]

Python indexa comenzando desde 0, de


manera que Hola es el elemento 0, Adios
el 1 y Buenas Tardes el 2, y la longitud de
la lista es 3. Podemos comprobarlo de esta
forma:
>>> a[1]
Adios
>>> len(a)
3

Es posible aadir elementos a las listas de


varias maneras. Si miramos el Listado 2,
veremos la ms sencilla. Las listas tambin
se pueden comportar como una Pila, con
las operaciones append y pop. Con insert
introducimos un elemento en la posicin
especificada (recuerda que siempre
comenzamos a contar desde 0). La facili-

15 [2, 57, 1, 3]
16 >>> b.append(1)
17 >>> b
18 [2, 57, 1, 3, 1]
19 >>> b.count(1)
20 2
21 >>> b.index(57)
22 1
23 >>> b.sort()
24 >>> b
25 [1, 1, 2, 3, 57]
26 >>> b.reverse()
27 >>> b
28 [57, 3, 2, 1, 1]

dad con la que Python trata las listas nos


permite usarlas para multitud de tareas, lo
que simplificar mucho nuestro trabajo.
A pesar de su potencia, las listas no pueden hacerlo todo, existiendo otra estructura que rivaliza con ellas en utilidad, los
Diccionarios. Mientras las listas nos permiten referenciar a un elemento usando un
nmero, los diccionarios nos permiten
hacerlo con cualquier otro tipo de dato.
Por ejemplo, con cadenas, de hecho, casi
siempre con cadenas, de ah que su nombre sea diccionario (vase el Listado 3).
Las listas y los diccionarios se pueden
mezclar: diccionarios de listas, listas de
diccionarios, diccionarios de listas de diccionarios, etc. Ambas estructuras combinadas poseen una enorme potencia.

Algoritmos + Estructuras de Datos


= Programas
Ahora nos toca poner todo esto en prctica. Lo normal es hacer un programa sencillo. Pero en lugar de eso vamos a implementar algo que sea creativo. Este programa es el que se usa en el libro La prctica de la programacin de Pike y Kernighan para ilustrar cmo un buen diseo
sobrepasa al lenguaje que usemos para eje-

Listado 3: Ejemplo Diccionario


01 >>> dic = {}
02 >>> dic[Perro] = hace guau
guau
03 >>> dic[Gato] = hace miau
miau
04 >>> dic[Pollito] = hace pio
pio
05 >>> dic
06 {Perro: hace guau guau,
07 Gato: hace miau miau,
08 Pollito: hace pio pio}
09 >>> dic[Perro]
10 hace guau guau

W W W. L I N U X - M A G A Z I N E . E S

INTRODUCCIN

Primeros Pasos

cutarlo. En el libro se implementa el diseo


en C, C++, Java, Perl y AWK. Nosotros lo
haremos en Python (ver Listado 4).
El programa acepta un texto como
entrada y genera un texto como salida, pero
este segundo texto no tiene sentido. Lo que
queremos hacer es generar texto sin sentido
pero con estructuras que s lo tengan. Puede
parecer algo muy complicado, pero no lo es
tanto si usamos la tcnica de cadenas de
Markov. La idea es coger 2 palabras, elegir
una palabra que suceda a cualquiera de las
dos y reemplazar la primera por la segunda
y la segunda por la palabra escogida. De
esta manera vamos generando un texto que,
aunque carece de sentido, normalmente se
corresponde con la estructura de un texto
normal aunque disparatado.

Para hacer las pruebas es recomendable


conseguir un texto de gran tamao. En textos pequeos no surtir tanto efecto. En el
proyecto Gtenberg podemos conseguir
infinidad de textos clsicos de enorme
tamao en ASCII. Pero somos conocedores
de que no todo el mundo entiende el
idioma anglosajn, as que en lugar de ir al
proyecto Gtenberg, podemos coger cualquier texto que queramos modificar, por
ejemplo, alguna noticia de poltica de un
diario digital o alguna parrafada de algn
blog.
Este programa es interesante porque permitir utilizar las estructuras de datos que
Python implementa, en particular en los
diccionarios, que generan una tabla donde
los ndices sern cadenas de texto.

Listado 4: markov.py Genera un Texto No-Tan-Aleatorio


01 #!/usr/local/bin/python
02
03 #Importamos dos mdulos
04 #random [que hace]
05 #y sys [que hace]
06 import random
07 import sys
08
09 no_palabra = \n
10 w1 = no_palabra
11 w2 = no_palabra
12
13 # GENERAMOS EL DICCIONARIO
14 dict = {}
15
16 for linea in sys.stdin:
17 for palabra in linea.split():
18 dict.setdefault( (w1, w2), []
).append(palabra)
19 w1 = w2
20 w2 = palabra
21

22 # Fin de archivo
23 dict.setdefault((w1, w2), []
).append(no_palabra)
24
25 # GENERAMOS LA SALIDA
26 w1 = no_palabra
27 w2 = no_palabra
28
29 # puedes modificarlo
30 max_palabras = 10000
31
32 for i in xrange(max_palabras):
33 nueva_palabra =
random.choice(dict[(w1, w2)])
34
35 if nueva_palabra == no_palabra:
36 sys.exit()
37
38 print nueva_palabra;
39
40 w1 = w2
41 w2 = nueva_palabra

Listado 5: Salida de markov.py


Para empezar, debemos saber por qu Python es obligatorio hacerlo de una
manera. Como es un lenguaje tipo script, debemos poner #! seguido de la
ejecucin del programa. Tambin tenemos a nuestra disposicin los operadores
habituales de otros lenguajes: +, -, *, /, etc. Y una vez que sabemos cmo
manejar operadores y asignaciones, se pueden mezclar: diccionarios de listas
de diccionarios, diccionarios de listas, listas de Python tambin vienen
provistas de muchas ms opciones que sus semejantes en otros lenguajes. Por
ejemplo, vamos a definir una lista que guarde una serie de espacios (logrados
pulsando la tecla TABULADOR) y una sentencia. Esos espacios son vitales, ya
que marcan la existencia de un texto que, aunque carece de sentido,
normalmente se corresponde con la que Python trata las listas de
diccionarios, diccionarios de listas, listas de Python resultan tan
potentes, es que nos permiten referenciar a un elemento en la sangra de la
OOP. - Es potente. Realizar un programa de 1000 lineas en Java, en Python es
obligatorio hacerlo de una manera. Como es un diccionario con dos palabras
como ndice y las palabras que les siguen en el que muchas entradas
referenciarn a una lista de ms de un bloque de cdigo Python. Ese bloque
aparecer en bucles, funciones o mtodos. Si nos fijamos bien en la sangra de
la memoria a bajo nivel como C con el tamao del texto que se usa en el Listado
1.

W W W. L I N U X - M A G A Z I N E . E S

Veamos cmo funciona. Lo primero es ir


introduciendo en el diccionario dos prefijos como ndice y las palabras que les
siguen en el texto dentro de una lista referenciada por ellos. Eso es un diccionario
con dos palabras como ndice que contiene
una lista:
DICCIONARIO[ palabra 1, U
palabra 2] -> U
LISTA[palabra,...]

O sea, si tenemos las palabras La gente


est La gente opina, crearemos un diccionario de la siguiente forma:
>>> dict[La, gente] = U
[est]
>>> dict[La, gente].U
append(opina)
>>> dict[La, gente]
[est,opina]

Vamos haciendo esto de manera sucesiva


con todos los conjuntos de dos palabras, y
obtendremos un diccionario en el que
muchas entradas referenciarn a una lista
de ms de un elemento.
La magia aparece cuando generamos el
texto, puesto que lo que hacemos es
comenzar por las dos primeras palabras, y
cuando existan varias posibilidades para
esa combinacin (como con el ejemplo de
La,gente), escogeremos aleatoriamente
entre ellas. Imaginemos que escogemos
opina, entonces escribimos opina por la
pantalla y buscamos en gente, opina y
as sucesivamente, hasta llegar a no_palabra.
Para entender mejor el funcionamiento
del programa recomendamos copiar el
cdigo fuente y pasarle unos ejemplos
(pongamos,
con
cat
texto.txt
|
./markov.py) y ver los resultados. En el
Listado 4 vemos un ejemplo de la salida
utilizando el texto de este artculo. El texto
generado casi tiene sentido, pero no del
todo.
Despus podemos intentar cambiar
cosas en el programa, por ejemplo, en
lugar de utilizar 2 palabras como ndice del
diccionario, podemos probar con 1 o con
3, y tambin con el tamao del texto que
se le pase. Se pueden conseguir cosas muy
interesantes.

Recursos
[1] Python: http://www.python.org

PYTHON

INTRODUCCIN

Ficheros

Manejo bsico de ficheros

Ruslan Olinchuk - 123RF.com

lbum
Fotogrfico
Siguiendo con nuestro paseo por Python, vemos
caractersticas bsicas, y concretamente en este
artculo, el tratamiento de ficheros. Por Jos Mari Ruz

En nuestro primer artculo vimos algo


sobre cmo trabajar con objetos en
Python. Fue muy simple, pero ya nos
daba la posibilidad de organizar nuestro
cdigo en torno a ellos. Python hace un
uso extensivo de los objetos en sus APIs,
y especialmente del control de errores
mediante excepciones, lo que nos da la
opcin de lanzarlas cuando algo va mal.
Una excepcin es un mensaje que podemos capturar cuando se ejecuta cierta
funcin o mtodo y aparece un error de
algn tipo. Normalmente controlamos
estos errores mediante el valor devuelto
por la funcin (como por ejemplo en C).
Esta tcnica es engorrosa, pero al igual
que todo, tiene sus virtudes y sus desventajas. Pero Python hace uso de las
excepciones en su lugar.
Cuando una funcin genera una
excepcin, decimos que eleva una excepcin. Es muy normal tener que controlar
las excepciones en las operaciones que
realicemos con recursos que pueden no
estar disponibles. Por eso las vamos a
ver, puesto que aqu vamos a trabajar
con archivos y conexiones a Internet.
Crearemos un objeto que gestione un
recurso que puede no estar disponible.
En este caso el objeto gestiona una variable (vase el Listado 1).
Alguien puede crear un objeto de la
clase obj_variable y llamar al mtodo
set_variable(23), pero cmo puede estar
seguro de que la variable var tiene el
valor 23 despus de la llamada? Puede
que var no tuviese el valor inicial de 0,
porque otra llamada anterior ya podra
haberla asignado. Lo nico que podra-

10

PYTHON

mos hacer es llamar a reset_variable() y


as asegurarnos de que nuestro valor sea
asignado, pero entonces destruiramos el
valor anterior y no sabramos qu podra
pasar.
Por lo tanto, necesitamos un mecanismo de comunicacin para darle a
conocer al usuario que esa variable ya
est asignada. Esto lo podemos hacer
con las excepciones.
En el Listado 2 aparece una clase que
hereda de la clase Exception llamada
Var_Asignada. Cuando en la clase
obj_variable intentamos asignar un valor
a la variable var y sta no es 0, entonces
se dispara, se eleva, la excepcin
Var_Asignada. Si no controlamos la porcin de cdigo en la que se encuentra
set_variable() y aparece una excepcin,
el programa se detendr y acabar.
La idea detrs de las excepciones es
que es posible tratarlas y evitar males
mayores, pudiendo en ocasiones incluso
recuperarnos de ellas. Para ello est la
estructura tryexcept, con la cual rodeamos el cdigo que puede disparar excepciones (Vase el Listado 3).
A partir de ahora, y hasta que no expliquemos con ms profundidad el tema de
las excepciones, cuando digamos que
una funcin genera una excepcin, significar que ese cdigo deber estar rodeado con una estructura tryexcept.

Trabajo con Ficheros


Ya que hemos conseguido cierta soltura
con los conceptos de objetos en Python,
ahora vamos a ver cmo se manejan los
accesos a ficheros en l.

Para acceder a un fichero, primero


necesitamos crear un objeto file. El
objeto file es parte de la librera base de
Python, as que no es necesario importar
ninguna librera.
>>> archivo = file(texto.txt)

Por definicin, file abre los ficheros en


modo de slo lectura. Eso significa que si
el fichero no existe, obtendremos un
error. Para verificar si el fichero existe
podemos usar la funcin exists() de la
librera os.path.
>>> import os.path >>>
os.path.exists(texto.txt) True
>>> os.path.extsts(algopeludo-y-feo.txt) False

Por lo tanto, si vamos a abrir un


fichero, podemos asegurarnos de que ya
existe.
Si en lugar de leerlo lo que queremos
es crearlo, deberemos invocar al constructor de file con los parmetros:
>>> archivo = file(texto.txt,U
w)

Este segundo parmetro opcional nos


permite definir el tipo de acceso que
vamos a realizar al fichero. Tenemos
varias posibilidades: podemos leer (r),
escribir (w), aadir al final del fichero
(a) y tambin tenemos el acceso de lectura/escritura (r+w). Disponemos tambin del modificador b para indicar
acceso binario. Por defecto, Python con-

W W W. L I N U X - M A G A Z I N E . E S

Ficheros

sidera todos los ficheros de texto. Vemos


todas las combinaciones en el Listado 4.
Si todo ha ido bien, con cualquiera de
estas llamadas tendramos en archivo un
objeto que gestiona el archivo indicado.
Ahora podemos operar sobre l.
Las operaciones ms tpicas son las de
leer desde el archivo y escribir en l. Para
ello, el objeto file dispone de los mtodos
read(), readline(), write() y writeline().
Todos ellos operan con cadenas de caracteres: readline() y writeline() trabajan
con lneas de texto (acabadas en retorno
de carro), mientras que read() y write()
lo hacen con cadenas sin restricciones.
Lo que vemos en el Listado 5 son
algunas manipulaciones sobre un
fichero. Lo primero que tenemos que
hacer es crear el fichero, para lo cual lo
abrimos en modo de escritura, w, que lo
crear o truncar el existente (lo borrar
para crearlo de nuevo. Si lo hubiramos
querido aadir al final, habramos usado
a). Posteriormente escribimos en l una
cadena con un retorno de carro en mitad
(para hacer nuestras pruebas) y cerramos el fichero. Es importante cerrar los
ficheros cuando dejemos de usarlos,
pero en este caso la razn para cerrarlo
es que vamos a volver a abrirlo en modo
de lectura.
Ahora volvemos a abrir el fichero en
modo de lectura, y leemos 4 bytes que
almacenamos en la variable cadena.
Cuando leemos con read(), avanzamos
en el fichero, siendo esta la razn de que
readline() que viene a continuacin lea
la cadena mundo\n en lugar de Hola
mundo. Tambin vemos que se para en
el retorno de carro en lugar de continuar.
El segundo readline() ya nos permite
leer la cadena Adis mundo.
Pero qu ocurrira si en una de las
lecturas nos encontrsemos con el fin de
fichero? En el caso de que leysemos una
cadena con el fin de fichero (EOF), al
final simplemente nos quedaramos con

Listado 1: Una Clase Python


01 class obj_variable:
02 __init__(this):
03 var = 0
04
05 set_variable(this, valor):
06 if (var == 0):
07 var = valor
08
09 reset_variable(this):
10 var = 0

W W W. L I N U X - M A G A Z I N E . E S

la cadena hasta el EOF. En cambio, si


slo leemos el EOF, entonces obtenemos
una null. Esto es importante para comprobar que hemos acabado con el
fichero. As, un bucle que escriba por
pantalla el contenido del fichero comprobara en cada vuelta si la cadena que
devuelve readline() es null.
Ahora que ya sabemos crear archivos,
tenemos que aprender a borrarlos. Esto
se realiza mediante la funcin remove()
de la librera os. Esta funcin acepta la
ruta de un fichero y lo borra. Si en lugar
de un fichero le pasamos un directorio
elevar una excepcin OSError.
>>> import os
>>> os.remove (texto.txt)
>>>

Directorios y Sistema de Ficheros


Con estos pocos mtodos tenemos ya a
nuestro alcance la manipulacin bsica
de ficheros. Pero vamos a necesitar para
nuestro programa la posibilidad de crear
directorios. Cmo lo haremos? Pues
mediante la funcin mkdir(), que acepta
una cadena y crea un directorio con ese
nombre. Si queremos crear un directorio
que est dentro de otros directorios tambin nuevos tenemos que usar makedirs(). Ambas funciones pertenecen al
mdulo os, por lo que para usarlas tendremos que hacer:
>>> import os
>>> os.mkdir(uno)
>>> os.makedirs(dos/tres)

INTRODUCCIN

y cuando la funcin fuese a borrar el


directorio dos, se encontrara con que
no puede porque existe dentro de l un
directorio llamado cuatro y parara.
Imaginemos ahora que necesitamos
cambiar el directorio en el que estamos
trabajando. En el momento de arrancar
el programa, el llamado directorio de
trabajo es decir, el directorio donde de
manera predeterminada se realizarn
todos los cambios es el directorio que
alberga el programa o bien el directorio
desde el que se ejecut. Pero, claro, no
siempre querremos que el programa utilice ese directorio.
Hay que tener en cuenta que, a no ser
que utilicemos rutas absolutas, cualquier
referencia a un fichero se tomar con
relacin al directorio de trabajo inicial.
Para poder cambiar el directorio de trabajo, el mdulo os tiene la funcin
chdir(). Si lo invocamos dentro de nuestro programa:
>>> os.chdir(/tmp)

Desde ese momento, cualquier referencia


a un fichero ser direccionada a /tmp.
Ahora podemos:
abrir, cerrar, modificar ficheros
crear, eliminar un directorio
cambiar el directorio de trabajo
Vamos a ir un poco ms all.

Llamadas a Otros Programas


A veces es ms sencillo usar una utilidad
del sistema operativo que crearla noso-

Listado 2: Uso de Excepciones


Para borrar esos directorios usaremos las
funciones rmdir() y removedirs(). La primera borra un directorio, mientras que
la segunda borra una ruta de directorios.
Vamos a ver esto con ms detenimiento.
>>> os.rmdir(uno)
>>> os.removedirs(dos/tres)

rmdir() borrar el directorio uno, que


no contiene ningn otro objeto en su
interior (ni directorios, ni ficheros). En
caso de tenerlo, la llamada devolvera un
error. La funcin removedirs() comenzara a borrar desde el directorio que est
ms a la derecha de la ruta (tres) hacia
el que est ms a la izquierda (dos).
Pero imaginemos que dentro de dos
tambin hay un directorio cuatro.
Entonces se borrara el directorio tres,

WWW.LINUX- MAGAZINE.ES

01 class Var_Asignada(Exception):
02 Excepcin que se dispara al
intentar asignar una variable ya
asignada en obj_variable
03 pass
04
05 class obj_variable:
06 Administra una variable
07 def __init__(self):
08 self.var = 0
09
10 def set_variable(self, valor):
11 if (self.var == 0):
12 self.var = valor
13 else:
14 raise Var_Asignada
15 def reset_variable(self):
16 self.var = 0
17
18 a = obj_variable()
19 a.set_variable(12)
20 a.set_variable(34)

PYTHON

11

INTRODUCCIN

Ficheros

Listado 3: Ms Excepciones
01 >>> try:
02 ... set_variable(12)
03 ... set_variable(34)
04 ... except:
05 ... print ERROR: Se ha intentado
asignar
06 ... print un valor a una
variable ya asignada
07 ...
08 ERROR: Se ha intentado asignar
09 un valor a una variable ya
asignada
10 >>>

tros, como por ejemplo, un procesado


usando tuberas en UNIX. Puede que
simplemente tenga que acceder a alguna
informacin como la que nos da uname.
El caso es que siempre es importante
tener la posibilidad de ejecutar otros programas desde nuestro programa Python.
Para ello usamos la funcin system del
mdulo os. Por ejemplo:
>>> import os
>>> os.system (uname -a)
Linux rachel 3.1.2-1.fc16.x86_64
#1 SMP Tue Nov 22 09:00:57 UTC 2011
x86_64 x86_64 x86_64 GNU/Linux
0
>>>

El parmetro que le pasamos a system es


una cadena con la instruccin Bash (en
este caso) y sus switches y flags. system
nos devuelve la salida de la instruccin (
Linux rachel 3.1.2-1.fc16.x86_64 #1 SMP
Tue Nov 22 09:00:57 UTC 2011 x86_64
x86_64 x86_64 GNU/Linux) y el estado de
salida resultante de la ejecucin de la instruccin (0 recurdese que 0 indica que
la instruccin ha acabado sin errores).

Python y la Web
Python posee gran cantidad de libreras
para trabajar con recursos de Internet.
De hecho, Django [1] , un servidor de
aplicaciones con gran xito, est creado
en Python y hace uso de todas sus caractersticas. Mailman [2] o Bittorrent [3]
son tambin buenos ejemplos.
Debido a su flexibilidad, Python es
usado como lenguaje de implementacin
para multitud de aplicaciones de red as
como aplicaciones distribuidas. Por eso,
no es de extraar que Python suela ser el
lenguaje en el que se implementan
muchas de las ms novedosas tecnologas de red.

12

PYTHON

En este apartado vamos a comenzar


Listado 4: Acceso a Ficheros
con lo bsico. Queremos traer un recurso
de la red a nuestra mquina, y para ello
01 archivo = file(texto.txt,r)
02 archivo = file(texto.txt,w)
emplearemos una URL del estilo http://
03 archivo = file(texto.txt,a)
www.algunaweb.algo/imagen.jpg. Pero
04 archivo =
primero necesitamos crear una conexin
file(texto.txt,r+w)
con el servidor.
05 archivo =
file(texto.txt,r+b)
Para ello vamos a utilizar la librera
06 archivo = file(texto.txt,rb)
httplib que viene de serie con Python.
Esta librera nos permite establecer una
programas. En UNIX es algo comn.
conexin con un servidor http y manPero cmo podemos obtener los pardarle comandos. Los comandos http
metros de ejecucin en Python? De
son simples, y de todos ellos slo nos
nuevo tenemos que recurrir a una libreinteresa uno, el comando GET. Cuando
ra: la librera sys.
accedemos a un servidor http, por
sys nos proporciona el acceso a los
ejemplo para ver una pgina web, lo
argumentos a travs de su variable argv.
que hacemos es pedirle objetos. Esto se
Esta variable es en realidad una lista, por
hace mediante el comando GET
lo que podemos obtener los argumentos
<objeto>. Por ejemplo, si queremos la
accediendo a las posiciones de la misma.
pgina index.html de la web http://
La posicin 0 contiene el nombre del
www.python.org, primero conectamos
programa que estamos ejecutando y, a
con el servidor http y despus, una vez
partir de la posicin 1, encontraremos
conectados, le enviamos el comando
los parmetros pasados. Al ser una lista,
GET index.html. En ese momento el
podemos conocer la cantidad de parmeservidor nos devuelve por el mismo
tros llamando a len().
canal el contenido del archivo
index.html.
Programa
Dicho as parece muy fcil, pero es
Ahora es el momento de poner todo lo
una tarea que en un lenguaje de ms
aprendido en prctica con un programa
bajo nivel requerira gran cantidad de
que puede ser til. En este caso vamos a
libreras y control de errores.
crear uno que realizar las siguientes
Lo primero es importar la librera
tareas:
httplib. Creamos entonces una conexin
El programa aceptar un parmetro de
con el host en cuestin y pedimos el
entrada que le indicar el nombre de
archivo index.html. Esa conexin genera
un fichero.
una respuesta. La respuesta est for El programa abrir ese fichero y lo
mada por varias partes, entre ellas un
leer lnea por lnea. Cada lnea del
cdigo numrico (como el famoso 404),
fichero ser la direccin URL de una
un texto que describe el error y una
imagen.
conexin al archivo que pedimos. En el
Cada URL ser introducida dentro de
caso de una conexin correcta recibireuna lista para su uso posterior.
mos un 200, un OK y una conexin con
el fichero. De esa conexin leemos con read() el contenido y
Listado 5: Lectura y Escritura de Ficheros
lo almacenamos en una varia01 >>> archivo = file(/tmp/texto.txt,w)
ble que llamamos dato. Enton02 >>> archivo.write(Hola mundo\nAdios
ces podremos cerrar la conemundo)
03 >>> archivo.close()
xin como si de un fichero se
04 >>>
tratara.
05 >>> archivo = file(/tmp/texto.txt,r)
En ese momento ya tenemos
06 >>> cadena = archivo.read(4)
la informacin que queramos
07 >>> cadena
en dato y el canal cerrado. No
08 Hola
es muy difcil, no? Veremos un
09 >>> cadena = archivo.readline()
ejemplo en el programa final de
10 >>> cadena
11 mundo\n
este artculo.

Paso de Parmetros
Estamos acostumbrados a
poder pasar parmetros a los

12 >>> cadena = archivo.readline()


13 >>> print cadena
14 Adios mundo
15 >>> archivo.close()

W W W. L I N U X - M A G A Z I N E . E S

Ficheros

INTRODUCCIN

Listado 6: Agarrafotos.py
001 #!/usr/bin/python
002
003 # ---NOTA-------------------------------------004 # El fichero que debe ser pasado como argumento
005 # debe consistir en un listado con una url por
006 # lnea.
007 # --------------------------------------------008
009 class Lista_URLs:
010 Recibe un fichero y carga sus cadenas en una
lista. Provee de mtodos para obtener de nuevo las
cadenas desde la lista.
011
012 def __init__(self,nombre):
013 # La lista donde guardaremos las URLs
014 self.lista= []
015 # El contador que usaremos para comprobaciones
016 self.contador = 0
017
018 # pasamos el nombre del fichero menos el ltimo
carcter
019 self.archivo = file(nombre)
020 self.cadena = self.archivo.readline()
021
022 while(self.cadena != \n):
023 #Metemos la cadena en la lista
024 self.lista.append(self.cadena)
025 self.cadena = self.archivo.readline()
026 self.archivo.close()
027
028
029 def rebobina(self):
030 # Hace que se comience de nuevo
031 # por el principio en la lista.
032 self.contador = 0
033
034
035 def siguiente(self):
036 # Devuelve el siguiente elemento o
037 # en caso de llegar al final.
038 if ( self.contador >= len(self.lista)):
039 return
040 else:
041 self.valor = self.lista[self.contador]
042 self.contador = self.contador + 1
043 return self.valor
044
045 def fin(self):
046 # Comprueba que hemos llegado al final
047 # de la lista. Preguntamos si hemos llegado
048 # al final antes de avanzar.
049 return (self.contador == len(self.lista))
050
051 def crea_directorio(cadena):
052 # Comprueba si el directorio especificado por
053 # cadena existe, en caso contrario lo crea
054 # y cambia el directorio de trabajo
055 # al directorio creado.
056
057 componentes = cadena.split(.)
058
059 if(os.path.exists(componentes[0])):
060 print Error: el directorio ya existe
061 sys.exit()
062 else:
063 # Creamos el directorio

W W W. L I N U X - M A G A Z I N E . E S

064 os.makedirs(componentes[0])
065 os.chdir(componentes[0])
066 print Creando directorio + componentes[0]
067
068 def descarga_urls(lista):
069 # Recorre la lista de urls usando el objeto
070 # Lista_URLs, las descarga y despus las
071 # guarda en ficheros con el mismo nombre que
072 # el de la imagen.
073
074 lista.rebobina()
075
076 while( not lista.fin() ):
077 url = lista.siguiente()
078
079 # dividimos la url en dos partes
080 # lo que descargamos y la url http
081
082 # Componentes es una lista que contiene
083 # las cadenas resultantes de trocear la
084 # cadena de texto de la URL usando /
085 # como separador. Por ejemplo:
086 # http://www.python.org/index.html
087 # componentes = [http:, , www.python.org,
088 # index.html]
089 componentes = url.split(/)
090 servidor = componentes[2]
091
092 # Construimos la ruta de la imagen, que
093 # consiste en toda la ruta si eliminamos
094 # al servidor y a http://
095 ruta_imagen = /
096 for i in range( 3, len(componentes)):
097 ruta_imagen = ruta_imagen + / + componentes[i]
098
099 # Descarga el fichero y lo guarda con el nombre.
100 # El nombre se saca de la URL.
101 # url[:-1] es la cadena url menos el ltimo carcter.
102 print Descargando imagen: + url[:-1]
103 conexion = httplib.HTTPConnection(servidor)
104 conexion.request(GET, ruta_imagen)
105 respuesta = conexion.getresponse()
106 # datos contiene ahora la imagen y la guardamos
107 datos = respuesta.read()
108 conexion.close()
109
110 # el nombre del fichero es el ltimo elemento
111 # de la lista componentes
112 nomb_fichero = componentes[len(componentes) -1]
113 # eliminamos el \n final
114 nomb_fichero = nomb_fichero[:-1]
115
116 # Abrimos el fichero, escribimos y cerramos
117 archivo = file(nomb_fichero ,w)
118 archivo.write(datos)
119 archivo.close()
120
121 def genera_index(lista):
122
123 # Crea un fichero index.html.
124 # Genera la cabecera, recorre la lista de URLS
125 # y por ltimo escribe el pie.
126 # Es posible mejorarlo introduciendo separadores
127 # o ttulos entre las imgenes ;)
128

PYTHON

13

INTRODUCCIN

Ficheros

Listado 6: Agarrafotos.py (Cont.)


129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154

print Generando ndice index.html


archivo = file(index.html,w)
# Cabecera
archivo.write(<html>\n)
archivo.write(<head>\n)
archivo.write(<title> Imagenes </title>\n)
archivo.write(</head>\n)
archivo.write(<body>\n)
archivo.write(<h1>Imagenes</h1>\n)
archivo.write(<ul>\n)
# siempre antes de recorrer:
lista.rebobina()
url = lista.siguiente()
# Dividimos la URL para poder utilizar
# partes de ella.
componentes = url.split(/)
imagen = componentes[len(componentes) - 1]

# Recorremos las urls


while( url != ):
# Imagen en HTML
archivo.write(<li><img src=\+ imagen
+\></img></li>\n)
155 url = lista.siguiente()
156 componentes = url.split(/)
157 imagen = componentes[len(componentes) - 1]
158
159 # ... y por ltimo el pie.
160
161 archivo.write(</ul>\n)
162 archivo.write(</body>\n)
163 archivo.write(</html>\n)
164
165 archivo.close()
166
167 #------------------------------------------------

Una vez que hayamos acabado de leer


el fichero, lo cerraremos y entraremos
en la segunda parte del programa.
Crearemos un directorio con el nombre del archivo que nos hayan dado.
Cambiaremos el directorio de trabajo a
ese directorio.
Descargaremos cada una de las URLs
dentro del directorio.
Generaremos un archivo index.html
que muestre las imgenes.
Mucho trabajo? Para eso estn los programas. Evidentemente no realizaremos
todas las comprobaciones que seran
necesarias, ya que en tal caso el programa se alargara demasiado, por lo que
se deja al lector la opcin de incluir
mejoras. Pensemos ahora en su diseo.
Tenemos varias partes:
Comprobar y almacenar la opcin con
el nombre del archivo.

14

PYTHON

168 # Main
169 #-----------------------------------------------170
171 # Esta es la tcnica estndar para organizar el
172 # cdigo en Python, se usa la siguiente construccin
173 # como punto de arranque.
174
175 if __name__ == __main__:
176
177 import httplib
178 import os
179 import os.path
180 import sys
181
182 # Comprobamos los argumentos...
183
184 if len(sys.argv) == 2:
185 #Pasamos el fichero al constructor
186 lista = Lista_URLs(sys.argv[1])
187
188
189 crea_directorio(sys.argv[1])
190
191 descarga_urls(lista)
192
193 genera_index(lista)
194
195 elif len(sys.argv) == 0:
196 # Vaya, han ejecutado sin poner argumentos...
197 # les recordaremos como va esto ;)
198 print La sintaxis del programa es:\n
199 print sys.argv[0] + archivo\n
200 print El archivo debe contener una URL por lnea
201
202 else:
203 # Alguien se ha quedado corto y se ha pasado
204 # con el nmero de argumentos.
205 print ERROR: la sintaxis es + sys.argv[0] +
<fichero>

Leer las URLs.


Crear Directorio y cambiar el directorio de trabajo.
Descargar las URLs.
Generar el archivo HTML.
Seguiremos estos puntos para crear las
funciones. Las URLs las almacenaremos
en una lista. Deberamos usar objetos?
Esta es una de las cosas maravillosas que
ofrece Python: NO estamos obligados a
usar objetos. Y no digo que los objetos
sean malos, sino que en ocasiones pueden llegar a ser engorrosos. Por ejemplo,
podramos crear un objeto Lista_URLs
que aceptase como parmetro en su
constructor el nombre de un fichero y
que despus nos permitiese ir cogiendo
las URLs una detrs de otra. Tambin
podemos hacer lo mismo usando una
funcin que cargue las URLs en una
variable global. Aqu vamos a hacerlo

con un objeto. Es en este momento


cuando se deja al lector que explore la
posibilidad de sustituir el objeto por una
variable global y las funciones de lista.
Este programa es muy simple, pero de
nuevo retamos a los lectores a mejorarlo
y a introducirle, por ejemplo, control de
excepciones.
Suerte.

Recursos
[1] La infraestructura Django para aplicaciones web:
https://www.djangoproject.com/
[2] El programa administrador de listas
de correo Mailman: http://www.gnu.
org/software/mailman/
[3] El programar para administracin de
Torrente BitTorrent:
http://bittorrent.com/

W W W. L I N U X - M A G A Z I N E . E S

Debugging

INTRODUCCIN

Sebastian Kaulitzki - 123RF.com

Eliminacin de bugs de programas Python

Desparasitando
Serpientes
Da igual lo buenos programadores que seamos, tarde o temprano daremos con ese BUG que
ser nuestro peor enemigo. Veamos cmo podemos emplear herramientas para derrotarlo
con mayor facilidad. Por Jos Mara Ruz

Equivocarse es humano, y a pesar de


todo el mito que rodea a los programadores, hasta el mejor de ellos comete errores diariamente. En muchas ocasiones es
mucho ms complicado eliminar un BUG
que crear el propio programa. Cuenta le
leyenda que el nombre de BUG viene de
la misma palabra que en ingls significa
bicho. Dicen que los primeros ordenadores eran grandes mquinas que generaban gran cantidad de calor, por lo que
innumerables insectos y otras alimaas
se introducan en ellos. De vez en
cuando, alguno tocaba dos cables y quedaba frito, provocando un fallo en el sistema. Actualmente se conoce como BUG
a todo error o situacin no controlada
que impida a un programa realizar tu
tarea con normalidad. Lo cierto es que
estamos bastante acostumbrados a que
los BUGS sean parte de nuestra vida.
Ventanas que no se cierran, programas
que consumen todo el espacio en memoria o videojuegos que se quedan bloqueados.
Python es un lenguaje dinmico, como
muchos otros. La principal ventaja es
que nos permite programar a alto nivel,
desentendindonos de toda la gestin de
recursos a bajo nivel que hace tan

W W W. L I N U X - M A G A Z I N E . E S

pesada la programacin en otros lenguajes como por ejemplo C. Pero no todo el


monte es organo. Tambin hay una
parte negativa: Python no es un lenguaje
demasiado estricto. Podemos hacer lo
que queramos con las variables sin que
el intrprete se queje hasta el ltimo
momento. Esta caracterstica impide la
posibilidad de verificar automticamente
todo el cdigo en el momento en que es
compilado. Un cdigo totalmente errneo, en el que por ejemplo se suman
letras y nmeros, puede pasar desapercibido en nuestro programa hasta el da
que se ejecuta y genera un error que
dejar al usuario con la boca abierta, y
ciertas dudas sobre nuestra vala como
programadores.
Casi a la vez que surgieron los lenguajes de programacin aparecieron unos
programas que han ido unidos a ellos:
los debuggers. Exiten muchos debuggers
diferentes. GNU desarroll DDD, pero
hace tiempo que no se ve actividad en
este proyecto (ver Recurso [1]). Valgrind
ha conseguido mucha fama en proyectos
que emplean C++ ( ver Recurso [2]).
En este artculo vamos a echar un vistazo a las herramientas que podemos
usar para localizar los fallos en nuestros

programas Python, y en particular a la


que viene de serie con Python: el PDB,
Python DeBugger (podemos ver la documentacin de PDB en el Recurso [3]).

Un Bug, Dos Bugs, Tres Bugs


Los lenguajes dinmicos, como decamos antes, tienen sus propias virtudes y
desventajas. Los programadores ms
duros suelen decir que programar con
lenguajes dinmicos es como jugar con
juguetes: no hay bordes cortantes con
los que cortarse ni partes pequeas con
las que atragantarnos. Vamos, que son
poco menos que una versin infantil de
los lenguajes serios. Lo cierto es que
hay mucha gente que no entra en estos
debates. Yo, por lo menos, prefiero hacer
un programa tan rpido como sea posible y espero que funcione casi a la primera.
De dnde salen los BUGS? Lo ms
probable es que haya siempre una variable implicada. La explicacin es simple:
las variables son la nica parte del programa que realmente no controlamos.
Mientras el resto del cdigo hace exactamente lo que le indicamos que haga, por
ejemplo abrir un fichero, en las variables
suceden todo tipo de cosas mientras el

PYTHON

15

INTRODUCCIN

Debugging

programa est en ejecucin. El problema


es que no vemos esas variables mientras
el programa est funcionando, as que
tenemos que imaginarnos qu est
pasando.
En condiciones ideales se puede perder el tiempo tratando de localizar los
fallos a ojo de buen cubero, pero bajo
estrs y con plazos, toda ayuda es poca.
Python adems nos permite almacenar
cualquier valor dentro de una variable.
Las variables en los lenguajes dinmicos
como Python son casi mgicas. En ellas
podemos almacenar un nmero:
>>> mivariable = 32

Y punto seguido almacenar un objeto:


>>> class Persona:
... def __init__(this,nombre):
... this.nombre = nombre
...
>>> mivariable = PersonaU
(Carlos)

Y sigue siendo la misma mivariable, pero


de alguna manera su naturaleza ha cambiado. Ahora imagina que esta situacin
ocurre en un programa que has creado.
Mientras tecleas piensas, mivariable
contiene una distancia y operas con ella,
slo que, sin que te des cuenta, en realidad mivariable contiene un objeto de la
clase Persona qu pasara si intentas
sumarle 18?
>>> a + 18
Traceback (most recent call last):
File <stdin>, line 1, in ?
TypeError: unsupported operand
type(s) for +: instance and int
>>>

ERROR! El intrprete de Python nos


advierte de que algo no marcha bien:
hay un error de tipo, no es posible
sumar un nmero entero y una instancia
de un objeto tal como hemos definido la
clase. Desagradable verdad? Por lo
menos este tipo de BUG, que es tan fcil
de encontrar que el propio intrprete de
Python lo encuentra. Qu ocurra si el
programa no fallase, sino que no nos
diese el resultado esperado? Y si el programa le dijese a un cliente que su edad
actual es -323134.32 aos? Este es sin
duda el peor tipo de BUG existente: el
semntico.
El intrprete de Python es perfecto
localizando errores sintcticos, aqullos
que tienen que ver con las propias palabas que escribimos. Si hacemos referencia a una variable que no est definida,
Python trata de buscar su valor, ve que
no existe la variable y se queja.
Los errores semnticos son harina de
otro costal, porque se refieren a fallos que
aparecen debido a que no se entiende lo
que est pasando. Un ejemplo simple es
el que hemos visto antes, cuando hemos
tratado de sumar una variable con una
instancia de un objeto que no responde a
la suma con un nmero.
Solucionar un BUG semntico puede
llevar desde segundos a aos. De hecho,
existe mucho software, tanto comercial
como libre, con BUGS que no han sido
resueltos en aos. Ahora que el problema ha sido planteado con ms detenimiento, conviene ver con qu arsenal
contamos en nuestra batalla contra los
BUGS.

Depurando Cdigo
Digamos que estamos haciendo un script
para mostrar, usando como caracteres las

Listado 1: El Cdigo Nefasto


01 #!/usr/local/bin/python
02
03 import pdb
04
05 def vacaciones (l):
06 cadena =
07 for i in range(1,31):
08 encontrado = False
09 max = len(l)
10 k=0
11 while(not(encontrado) or
k<max):
12 rango = l[k]
13 inf,sup=rango

16

PYTHON

14 if ((i >= inf) and (i <= sup)):


15 encontrado = True
16 else:
17 k+=1
18
19 if (encontrado):
20 cadena += #
21 else:
22 cadena +=
23
24 return cadena
25
26 pdb.run(vacaciones([(1,3),(6,10)]))

Figura 1: A quin dejaremos sin vacaciones?

vacaciones que han escogido una serie de


empleados de una empresa. Como no
queremos que el cdigo sea largo ni
demasiado complicado, lo hemos reducido al mnimo. La funcin tomar una
lista de tuplas, que representa las vacaciones de una persona en un mes. Cada
tupla representa un periodo de vacaciones, con una fecha de inicio y una fecha
de fin. La idea es muy simple, aceptamos
esa lista y despus devolvemos una
cadena donde los das de trabajo se
representan por un espacio, y los de
vacaciones con un #. No es muy complicado verdad?
As que nos ponemos manos a la obra,
es algo sencillo, no nos llevar ni 10
minutos, acabamos escribiendo el
cdigo del Listado 1. Pero nos interrumpen antes de probar la funcin, y
cuando volvemos al ordenador y ejecutamos un programa de prueba se queda
bloqueado! No puede ser!, en tan pocas
lneas de cdigo no puede haber un
error tan grave. Despus de unos minutos de frustracin, abandonamos la inspeccin visual y pasamos a trabajar
PDB.
PDB es el Python DeBugger y viene de
serie con Python. Eso est muy bien,
porque en caso de necesitarlo siempre lo
tendremos a mano. A diferencia de otros
debuggers, PDB se puede usar como si
fuese una librera. Podemos integrarla en
nuestros programas y eliminarla cuando
ya los hayamos arreglado. Como es posible observar en el Listado 1, hemos
importado PDB y hemos pasado como
parmetro a pdb.run() una cadena en la
que invocamos la funcin vacaciones()
con un argumento acorde. Si ejecutamos
el programa, veremos lo siguiente en
nuestro terminal:
josemaria@linuxmagazine$U
./p.py
> <string>(1)?()
(Pdb)

W W W. L I N U X - M A G A Z I N E . E S

Debugging

INTRODUCCIN

Podemos ver lo que hace un comando


usando de nuevo la letra h:

Figura 2: IDLE no es bello, pero es prctico.

Muy bien, el PDB comienza a hacer su


trabajo. En lugar de no hacer nada, como
hara la funcin vacaciones() si el programa se limitase a ejecutarla, entramos
en lo que parece el prompt del shell de
PDB. Esta shell tiene sus propios comandos, no tiene nada que ver con Python.
Para ver los comandos disponibles podemos emplear el comando h:
(Pdb) h
Documented commands (type help
<topic>):
================================
EOF break condition disable help
list q step w
a bt cont down ignore n quit
tbreak whatis
alias c continue enable j
next r u where
args cl d exit jump p return
unalias
09 b clear debug h l pp s up
Miscellaneous help topics:
==========================
exec pdb
Undocumented commands:
======================
retval rv
(Pdb)

Buff! estos son demasiados comandos.


Como suele ocurrir la primera vez que
un principiante en Linux pulsa dos
veces tabulador en BASH, lo que vemos
nos asusta. En este caso no son tantos
comandos (en un Linux estndar hay
miles de ejecutables), pero s ms extraos. Debuggear es algo que SIEMPRE se
hace bajo presin, por lo que todo el
tiempo que ahorremos es oro. As que
los creadores de PDB nos ahorran
segundos reduciendo los comandos a
letras.

W W W. L I N U X - M A G A Z I N E . E S

(Pdb) h l
l(ist) [first [,last]]
List source code for the current
file.
Without arguments, list 11 lines
around the current line
or continue the previous listing.
With one argument, list 11 lines
starting at that line.
With two arguments, list the given
range;
if the second argument is less than
the first, it is a count.
(Pdb)

El comando l sirve para mostrar visualmente en qu parte del cdigo estamos


en un momento dado; como nos pica la
curiosidad ejecutamos l:
(Pdb) l
[EOF]

Vaya, resulta que no hemos comenzado


an, por lo que no estamos en ninguna
parte. El comando que probablemente
usemos ms a menudo es s Por qu?
pues porque es el comando que hace que
PDB avance una lnea y la ejecute:
(Pdb) s
--Call-> /home/josemaria/p.py(5)U
vacaciones()
-> def vacaciones (l):

Muy bien, comenzamos a ejecutar el


trozo de cdigo Python que pasamos a
pdb.run(). Por el momento no pasa nada
interesante, aunque estara bien ver qu
contiene la variable l, para ello podemos
usar el comando p que hace las funciones
de print:
(Pdb) p l
[(1, 3), (6, 10)]

Efectivamente, l contiene el parmetro


que hemos pasado a la funcin. Ya estamos en ruta, as que avancemos unos
cuantos pasos ms:
(Pdb) s
> /home/josemaria/p.py(6)
vacaciones()
-> cadena =

Figura 3: El depurador de IDLE.

(Pdb) s
> /home/josemaria/p.py(7)
vacaciones()
-> for i in range(1,31):
(Pdb) s
> /home/josemaria/p.py(8)
vacaciones()
-> encontrado = False
(Pdb) s
> /home/josemaria/p.py(9)
vacaciones()
-> max = len(l)
(Pdb)

Hemos avanzado 4 pasos y puede que


nos hallamos perdido. Dnde estamos?
Qu estamos haciendo? Hacia dnde
vamos? El comando l resuelve todas
nuestras dudas:
(Pdb) l
4
5 def vacaciones (l):
6 cadena =
7 for i in range(1,31):
8 encontrado = False
9 -> max = len(l)
10 k=0
11 while(not(encontrado)
or k<max):
12 rango = l[k]
13 inf,sup=rango
14 if ((i >= inf) and
(i <= sup)):
(Pdb)

Ya me sito, acabamos de entrar en el


bucle for, avancemos un poco ms:
(Pdb) s
> /home/josemaria/p.py(10)U
vacaciones()
-> k=0
(Pdb) s
> /home/josemaria/p.py(11)U
vacaciones()
-> while(not(encontrado) orU
k<max):
(Pdb)

PYTHON

17

INTRODUCCIN

Debugging

Bueno, llegamos a una encrucijada.


Cuando ejecutamos la funcin sin PDB,
parece como si el programa nunca acabase. Esto implica que hay algo que se
repite eternamente. En este programa hay
dos bucles, que son los nicos elementos
que pueden repetirse eternamente. El primero es un bucle for con un principio, 1,
y un fin, 31, por lo que podemos descartarlo como culpable. El segundo sospechoso es ese bucle while, que en un primer momento no tiene porqu acabar,
puede que jams pare. Si un bucle while
no para es porque las condiciones que lo
controlan siempre se dan. En este cdigo
se supone que el if dentro del bucle while
hace que ste pare alguna vez, as que
algo debe fallar ah dentro. Comencemos
comprobando los valores de las variables
que controlan el bucle:
(Pdb) p encontrado
False
(Pdb) p k
0
(Pdb) p max
2
(Pdb)

De acuerdo, todo parece en su sitio. Si


avanzamos un poco:
> /home/josemaria/p.py(12)
vacaciones()
-> rango = l[k]
(Pdb) s
> /home/josemaria/p.py(13)
vacaciones()
-> inf,sup=rango
(Pdb) s
> /home/josemaria/p.py(14)
vacaciones()
-> if ((i >= inf) and
(i <= sup)):
(Pdb) s
> /home/josemaria/p.py(15)
vacaciones()
-> encontrado = True
(Pdb) s
> /home/josemaria/p.py(11)
vacaciones()
-> while(not(encontrado) or
k<max):
(Pdb)

Resulta que hemos entrado en una tupla


que representa unas vacaciones, el da
representado por i pertenece a las vacaciones. Por tanto, hemos hecho que la

18

PYTHON

variable encontrado sea True. Si todo va


bien, el siguiente paso despus de evaluar las condiciones del while ser salir
del mismo y pasar a las siguiente instruccin fuera del while:
(Pdb) s
> /home/josemaria/p.py(12)U
vacaciones()
-> rango = l[k]
(Pdb)

Pero qu ocurre aqu? Esto no debera


pasar. La nica explicacin posible es
que la condicin del while est es eso
un or? Debera ser un and! Deberamos
salir si hemos encontrado que el da pertenece a un rango, o si no quedan rangos
que comprobar. Ah estaba nuestro BUG,
tres simples letras, se cambian y problema solucionado.
Ahora que ya sabemos lo que pasa,
slo queda salir del debugger usando el
comando q. Nuestro nuevo cdigo ya
est listo para ser usado (ver Figura 1).

Y Ahora de Forma Fcil


No hay una manera ms moderna de
conseguir esto mismo? Pues s, gracias a
IDLE (ver Recurso [4]).
IDLE es el entorno de desarrollo que
viene, tambin, junto a Python. En la
Figura 2 se puede observar el aspecto
que tiene IDLE una vez que se ejecuta.
No es ninguna belleza, pero es prctico,
reemplaza al intrprete de comandos de
Python y simplifica algunas tareas.
En particular, estamos interesados en
cmo puede simplificar el debugging.
Para ello slo tenemos que ir al men
Debug que aparece en la barra de men
de IDLE y activar Debugger. Aparecer
una ventana como la que puede observarse en la Figura 3. El lector no debe
extraarse demasiado con esta nueva
ventana, viene a condensar en un solo
lugar todo lo que hemos visto sobre
PDB: hay un botn llamado STEP, que
nos permitir avanzar en el cdigo paso
a paso, y tambin hay un rea llamada
Locals, donde iremos viendo el valor de
las variables que se vayan declarando en
el cdigo, de forma que podremos ir controlando la evolucin del programa de un
solo vistazo.
Para ello slo tenemos que cargar el
programa en IDLE y veremos cmo se
abre una especie de editor de textos
como el de la Figura 4, en el que debere-

Figura 4: El seudo-editor de IDLE.

mos seleccionar en el men Run la


opcin Run Module. Con este paso cargaremos el fichero y comenzaremos a ejecutarlo. Como antes seleccionamos la
opcin Debugger, IDLE se cargar en la
ventana de debugging y podremos
comenzar a visionar la evolucin del
programa conforme pulsemos sobre el
botn Step. No es que IDLE sea un gran
avance respecto al uso de PDB, pero
desde luego simplifica el debugging.

Conclusin
Los debuggers no son una excusa para
crear programas sin fijarnos demasiado
en los problemas. Pero si no tenemos
claro qu est ocurriendo o si ya no
sabemos qu hacer, entonces un debugger como PDB puede ayudarnos a tener
una imagen ms clara de lo que pasa en
nuestro programa. Existen bugs que no
pueden cazarse con PDB, pero son tan
extraordinariamente raros, que es posible que jams nos encontremos con
uno.
Los debuggers nos permiten programar sin miedo a no entender lo que estamos haciendo, y es precisamente eso lo
que nos permitir avanzar y aprender
ms rpidamente. Perdmosle el miedo
a los BUGS!

Recursos
[1] Documentacin del depurador PDB:
http://docs.python.org/lib/modulepdb.html
[2] Valgrind captura errores que comprometen la memoria en:
http://valgrind.org/
[3] Data Display Debugger:
http://www.gnu.org/software/ddd/
[4] El entorno de desarrollo IDLE:
http://www.python.org/idle/

W W W. L I N U X - M A G A Z I N E . E S

Funciones Lambda

AVANZADO

hs
rap
tog
ho
ep
tyl
s

Funciones lambda en Python

om
F.c
3R
12

Sin
Nombre

Python es un lenguaje de programacin multiparadigma, y las


funciones lambda son parte fundamental de l, aunque como
veremos, existen buenas razones para no abusar de ellas.
Por Jos Mara Ruz

Existe un gran misticismo en torno a


los conceptos de funcin lambda y al
concepto de cierre. Siempre que aparece
un nuevo lenguaje de programacin
suele venir acompaado de una discusin que demuestra que el nuevo lenguaje es mejor porque incorpora alguno
de estos dos conceptos. Quien est un
poco al tanto de los ltimos avances
habr escuchado decir que C# incorpora
las funciones lambda y que la prxima
versin java al fin las tendr entre su
arsenal.
Las funciones lambda reciben su nombre de la teora del clculo lambda de
Alonzo Church [1], que junto a Alan
Turing, sentaron las bases de la teora de
computacin. Mientras Turing utiliz en
su teora una mquina abstracta e imaginaria a la que se llam Mquina de
Turing, Alonzo utiliz un enfoque ms
tradicional, creando una serie de reglas
que permitan realizar computaciones.
Su sistema requera de un tipo de fun-

W W W. L I N U X - M A G A Z I N E . E S

cin especial, para la que us la letra


lambda. Las funciones lambda que veremos comparten slo algunas de las
caractersticas que Alonzo defini para
las suyas, y podemos decir que lo que las
define es que son annimas: las funciones lambda no tienen nombre.
Cmo es esto posible? Para qu querramos algo as? Son slo dos preguntas
que trataremos de resolver en este artculo mientras desmitificamos un concepto tan abstracto a primera vista.

Las Funciones Lambda


Las funciones lambda son el concepto
ms sencillo de los que vamos a ver en
este artculo. Python las soporta desde
hace un buen tiempo, y se encuentran
integradas en la librera base de Python.
Tambin conocidas como funciones annimas, no son ms que funciones sin
nombre que podemos crear en cualquier
momento y pasar como argumento a
otras funciones:

>>> a = lambda x : x + 1
>>> a(2)
3
>>>

Recordemos que en Python las variables


no son ms que nombres que asignamos
a cosas y no contenedores de esas cosas.
Esta diferencia es vital para comprender
por qu podemos asignar una funcin a
una variable y posteriormente asignar un
valor a la misma (ver Figura 1).
En este sencillo ejemplo hemos creado
una funcin que suma el nmero 1 al
nmero que pasemos como argumento.
La definicin de una funcin lambda
siempre comienza con la palabra lambda
seguida de los argumentos que vamos a
aceptar. Detrs de los argumentos usamos el smbolo : para separar la definicin del cuerpo de la funcin. Las funciones lambdas, al ser annimas, deben
almacenarse en una variable si queremos reutilizarlas, y se comportarn

PYTHON

19

AVANZADO

Funciones Lambda

como una funcin tradicional a la que


podremos llamar pasndole parmetros
entre dos parntesis ().
Existen varias restricciones en el uso
de las funciones lambda. La primera es
que siempre deben devolver un valor.
Son funciones en el sentido estricto de
las matemticas, aceptan valores, los
transforman y devuelven algn valor. En
Python podemos devolver varios valores
si lo deseamos:
>>> b = lambda x: (x,x+1)
>>> b(2)
(2,3)
>>>

La segunda restriccin es que slo pueden contener una expresin. Esta restriccin limita bastante el poder de las funciones lambda en Python. En Ruby, por
ejemplo, las funciones lambda (tambin
llamadas bloques) pueden contener tantas expresiones como deseemos. En
Python se decidi aadir esta restriccin
para que los desarrolladores terminaran
empleando las funciones lambda all
donde una funcin tradicional podra
valerles (en Javascript es algo que se
hace habitualmente). Cuando violemos
una de estas restricciones, Python generar una excepcin:
>>> c = lambda x: y = x+1
File <stdin>, line 1
c = lambda x: y = x+1
^
IndentationError: unexpected U
indent

En este caso hemos tratado de realizar


una asignacin dentro de una funcin
lambda. Lo que s podemos hacer es
pasar ms de un parmetro a la funcin:

>>> d = lambda x,y: x*y


>>> d(2,3)
6
>>>

Podemos, de forma limitada, emplear


sentencias condicionales, puesto que
Python nos permite usar la frmula
if...else como si fuese una expresin:
>>> d = lambda x: Yuju U
if x > 2 else ooooh
>>> d(2)
ooooh
>>> d(3)
Yuju

Pero Para qu Sirven?


Las limitaciones a las funciones lambda
en Python tienen un objetivo bien definido: evitar el mal uso que se puede
hacer de ellas. Se restringe su uso a
aquellas funciones donde es necesario
pasar operaciones sencillas que el diseador original de una funcin no puede
predecir de antemano. Por ejemplo, si
queremos ordenar una lista, la funcin
de ordenacin sorted() intentar comparar los elementos de la misma usando los
mtodos que existen por defecto. Pero
qu ocurre si los datos a ordenar son
algo especiales? Imaginemos que tenemos una lista de datos donde cada elemento es un tupla con el nombre y la
edad de una serie de personas:
>>> l = [(Luis, 65), U
(Juan,28),U
(Montse, 33)]

Cmo podemos indicar a sorted que


queremos ordenar la lista por edades? El
diseador de sorted no puede predecir
todas las posibles estructuras de datos
que se pasarn a la funcin. Existen tres
opciones, u obligamos a la
persona que pasa los datos
a encapsularlos en objetos
con un mtodo que nos
permita comparar dos
objetos:
01 from functools import
total_ordering
02
03 @total_ordering
04 class Edad(object):

Figura 1: Las variables en Python son nombres.

20

PYTHON

05 def __init__(self,
nombre,edad):

06
07
08
09
10

self.nombre = nombre
self.edad = edad
def __eq__(self, otro):
return cmp(self.edad,
otro.edad)

11
12 def __lt__(self, otro):
13 return self.edad <
otro.edad
14
15 def __str__(self):
16 return self.__repr__
17
18 def __repr__(self):
19 return u({0} tiene {1})
.format(self.nombre,
self.edad)
20
21 l = [Edad(Luis, 65),
22 Edad(Juan,28),
23 Edad(Montse, 33),]
24
25 print sorted(l)

O bien definimos una funcin que nos


permita extraer el valor a comparar de
los objetos:
>>> def mi_ordenacion (x): U
return x[1]
>>> sorted(l, key = U
mi_ordenacion)
[(Juan,28),(Montse, 33), U
(Luis, 65)]
>>>

O bien podemos emplear una funcin


lambda para generar los valores a comparar:
>>> sorted(l, key = U
lambda x: x[1])
[(Juan,28),(Montse, 33), U
(Luis, 65)]
>>>

La primera opcin, ms clsica de lenguajes como Java o C#, y (supuestamente) ms limpia, tiene un gran problema. Qu ocurre si queremos ordenar
los datos de varias maneras diferentes?
El diseador de sorted slo emplear una
de ellas. Es un enfoque bastante inflexible, es preferible que la funcin que
selecciona el criterio de ordenacin sea
externa al objeto a ordenar. Adems,
como se puede observar, esta opcin es
bastante ms compleja.

W W W. L I N U X - M A G A Z I N E . E S

Funciones Lambda

La segunda opcin implica el uso de


una funcin externa que nos devuelve el
valor a comparar para cada objeto. Indicamos a sorted qu funcin usar para
seleccionar los valores a comparar
mediante el parmetro key. Por ltimo
vemos cmo se hara lo mismo con una
funcin lambda. Salta a la vista que la
tercera opcin es la corta, fcil de leer y
elegante (o lo que es lo mismo, la ms
pythonic).
El lenguaje de programacin Common
Lisp se enfrent a este mismo problema
cuando se dise su sistema de objetos,
y la solucin fue la misma: sacar fuera
del objeto y de la funcin de ordenacin
el cdigo que genere los datos a comparar. Python emple la misma tcnica, por
lo que mucha gente ve semejanzas entre
ambos lenguajes de programacin.

Cierres
Otro de los conceptos que puede provocar ms de un dolor de cabeza es el de
cierre. Lenguajes como Javascript giran
en torno a este concepto, ya que les permite crear algo parecido a objetos, ver
Figura 2. En Python, sin embargo, los
cierres son la base de una de las caractersticas ms usadas del lenguaje ltimamente: los decoradores.
Un cierre es un trozo de cdigo fuente
que depende de variables, algunas de las
cuales han sido capturadas junto al
trozo de cdigo y quedan aisladas de
cualquier interferencia externa. En el
caso de Javascript, que no posee objetos
propiamente dichos, se usan cierres para
que una serie de funciones compartan
unas variables que no pueden ser accedidas desde fuera y que por tanto estn
protegidas.
En Python los cierres no funcionan
exactamente como lo hacen en otros lenguajes. Siguiendo con las funciones
lambda, vamos a crear un cierre con una
de ellas:
>>> def crea_cierre(num):
... return lambda x=num: x+1
...
>>> cierre = crea_cierre(3)
>>> cierre()
4
>>> 4

Analicemos este cdigo. La funcin


crea_cierre() acepta un parmetro num y
devuelve una funcin lambda, por lo que

W W W. L I N U X - M A G A Z I N E . E S

AVANZADO

el valor devuelto puede


almacenarse en una variable y se comportar como
una funcin. El secreto
est en pasar a la funcin
lambda el valor num que
queda definido como valor
por defecto para x. Lo
curioso es que en este caso
Python no nos permite
pasar ningn valor a la
funcin lambda una vez
definida. Si tratamos de
pasar un valor:
>>> cierre(10)
4
>>>

Nos sigue devolviendo 4! Figura 2: Cmo funciona un cierre en torno a una funcin.
No importa qu valor pase07 >>> saludo = Saluda(Hola)
mos a la funcin, el contenido de x ha
08 >>> saludo(mundo)
quedado completamente cerrado y blo09 Hola mundo
queado. Por as decirlo, su valor se ha
10 >>> saludo = Saluda(Hello)
vuelto inaccesible. Si quisiramos poder
11 >>> saludo(world)
cambiar el valor de una variable cerrada,
12 Hello world
deberamos usar una funcin normal en
13 >>>
lugar de una funcin lambda, porque
como ya vimos, las funciones lambda no
Creamos un objeto tradicional con la
aceptan asignacin de variables.
nica diferencia de poseer un mtodo
Tiene sentido usar cierres en Python?
llamado __call__, que ser el que se ejeEl sistema de objetos de Python es muy
cute cuando invoquemos la instancia de
sencillo y nada engorroso, por lo que es
la clase como si fuese una funcin. Primuy extrao ver el uso de cierres en
mero debemos generar una instancia a la
Python, salvo por una excepcin: los
que pasamos la variable de se cerrar y
decoradores. Un decorador es una funposteriormente podemos invocar la inscin que intercepta los parmetros de
tancia como si fuese una funcin que
otra funcin, hace algo y devuelve la
acepta parmetros como cualquier otra
funcin original. Normalmente no se ciefuncin.
rran variables en un decorador, pero es
posible hacerlo.
Funciones de Primer Orden
La respuesta de Python a los cierres de
Como ya hemos dicho, las funciones
funciones son los objetos callable. En
lambda no son especialmente potentes
Python es posible crear un objeto que se
en Python en Ruby lo son ms pero
comporte como una funcin. La funcin
nos permiten pasar comportamientos a
tendr acceso a una serie de variables de
otras funciones. En teora de lenguajes
instancia que se pasan en el constructor
de programacin, se llama funcin de
de la misma:
primer orden a aquella funcin que
01 >>> class Saluda(object):
acepta otra funcin como parmetro,
02 ... def __init__(self,
acepta comportamientos adems de
saludo):
datos, lo que la hace especialmente
03 ... self.saludo =
potente y flexible.
saludo
En Python es absolutamente normal
04 ... def __call__(self,
usar funciones como parmetros:
nombre):
05 ... print {0} {1}
.format(self.saludo, nombre)
06 ...

01 def saluda(x,
f=None):
02 if f:

PYTHON

21

AVANZADO

Funciones Lambda

03 print f(x)
04 else:
05 print x
06 >>>
07 >>> saluda(hola)
08 hola
09 >>> saluda(hola,
f=lambda x: x.upper())
10 HOLA
11 >>>

Al fin y al cabo, las variables en Python


no son ms que nombres que apuntan a
cosas, sin importar demasiado qu
son esas cosas. No hay gran misterio en
las funciones de primer orden vistas as.
Es lo que haces con ellas lo que las
vuelve interesantes. Python nos provee
de un conjunto de funciones de primer
orden, heredadas de otros lenguajes de
programacin funcionales, que nos permiten emplear funciones en sus operaciones.

Map y Reduce
Cuando las funciones lambda realmente
brillan es cuando se usan en conjuncin
con las funciones de filtrado y mapeo.
Con todo el revuelo generado por Google y otras empresas en torno a las tcnicas Map/Reduce, es interesante recordar
los humildes comienzos de estas tcnicas.
El filtrado y mapeo son tcnicas heredadas de la programacin funcional, ver
Figura 3. En este estilo de programacin
se evita modificar los datos originales, y
en lugar de ello se realizan una serie de
transformaciones sobre los datos para ir
reducindolos y operando sobre los
resultados hasta conseguir el resultado
deseado. El cdigo resultante suele ser
conciso, aunque no siempre fcil de leer
y entender. La programacin funcional
tambin hace uso de las funciones de
primer orden que hemos visto antes.
Comencemos por echar un vistazo a la
funcin map:
>>> map(lambda x,y: (x,y),U
[Luis, Juan,Montse], U
[65,28,33])
[(Luis, 65), (Juan, 28), U
(Montse, 33)]
>>>

map aplica una funcin a dos listas de


valores, recorriendo ambas listas de
valores y pasando los valores como par-

22

PYTHON

metros. Primero aplicar la


funcin a Luis y 65,
despus a Juan y 28 y
as indefinidamente. map
no est limitada a dos listas,
podemos emplear tantas listas como queramos:
>>> map(lambda x:
x.upper(),U
[Luis, JuanU
, Montse])
[LUIS, JUAN, U
MONTSE]
>>> map(lambda x,y,z:U
(x, y*z),U
[Luis, U
Juan,Montse],U
[65,28,33], [1,2,3])
[(Luis, 65),U
(Juan, 56),U
(Montse, 99)]
>>>

Aunque podramos pasar Figura 3: Map y Reduce en accin.


cualquier funcin previamayor de ellos. Por lo tanto, map aplica
mente definida, estamos pasando funciouna funcin a una gran cantidad de
nes lambda. Es la manera ms sencilla y
datos y reduce realiza alguna operacin
rpida de aplicar map, aunque como ya
sobre los datos que los convierte en un
hemos dicho antes, si necesitamos ms
solo valor.
de una operacin o el cdigo es complejo
A pesar de toda la fanfarria existente
es mejor definir la funcin.
alrededor de Map/Reduce, lo cierto es
Este map es el mismo que el del
que en Python se usan poco. De hecho,
famoso Map/Reduce de Google, con la
reduce dejar de ser una funcin primidiferencia de que el de Google se aplica a
tiva del sistema en Python 3 y pasar a
cientos o miles de mquinas, que aplican
funciones map muy complejas a gran
ser una funcin de la librera functools,
cantidad de datos. Pero el concepto es el
por lo que ha sido degradada a ciudamismo.
dano de segunda fila.
Si este es el map dnde est
Conclusin
reduce?
Estamos tan acostumbrados ya a la pro>>> reduce(U
gramacin orientada a objetos, que se
lambda x,y: x if x[1] > y[1] U
nos olvida que Python en sus inicios
else y,U
tom prestados gran cantidad de concep[(Luis, 65), (Juan, 56),U
tos y tcnicas de otros modelos de pro(Montse, 19)])
gramacin. Las funciones lambda no
(Luis, 65)
estn muy vistas como tcnica de pro>>>
gramacin, pero las restricciones a las
que las somete Python las ha domestireduce va a aplicar una funcin de 2
cado lo suficiente como para que en
variables a los elementos de una lista
lugar de entorpecer nuestro cdigo lo
con el objetivo de acabar devolviendo un
hagan ms ligero y sencillo de comprensolo elemento. Como su propio nombre
der.

indica, reduce una lista a un elemento.


Aqu estamos buscando el valor ms
Recursos
grande de los presentes en la lista, por lo
[1] Alonzo Church: http://es.wikipedia.
que la funcin reductora compara los
org/wiki/Alonzo_Church
dos parmetros y siempre devuelve el

W W W. L I N U X - M A G A Z I N E . E S

OpenOffice

INTEGRACIN

PyUNO: Explota todo el potencial de OpenOffice

Python no hay
ms que UNO
Has visto alguna vez a los brokers de bolsa? Recuerdas sus sofisticados y caros
programas para ver las cotizaciones de las empresas en bolsa en tiempo real? Nosotros haremos lo mismo con 70 lineas de cdigo Python, OpenOffice y la tecnologa
UNO de OpenOffice. Por Jos Mara Ruz

No es ni ser la ltima vez que desde


esta seccin recordemos que la idea original de Stallman era la de que cada
programa libre estuviese construido
sobre libreras de funciones, de manera
que su cdigo fuese reutilizable por
cualquier otro programa.
Quizs en un programa pequeo no
sea muy til este tipo de diseo, pero
qu pasa con esos monstruos consumidores de memoria que rondan por
nuestros discos duros? Nos referimos a
programas o entornos del calibre de
Gnome, KDE, Mozilla u OpenOffice.
Todo el mundo se queja de su tamao
excesivo, su alto consumo de recursos
y su inexplicable complejidad.
Quizs con este artculo desmintamos este mito y hagamos que el lector
mire con nuevos ojos a estos maravillosos programas.

Grandes Sistemas de Componentes


El diseo de un gran programa puede
llevar aos y cientos o miles de programadores. Organizar tal cantidad de per-

W W W. L I N U X - M A G A Z I N E . E S

sonas supone ya una locura slo por el


hecho de asegurarse que todos cobren.
Pero vayamos a nuestro mundillo
cmo podemos organizarlos para que
el desarrollo no acabe en un fiasco?
Esta es la gran cuestin no resuelta
de la informtica, pero, aunque no
hayamos encontrado una solucin
fiable, s se disponen de tcnicas que
aumentan la probabilidad de que, al
menos, se cree algn software til.
Una de estas tcnicas consiste en
emplear un sistema de componentes
como base para el desarrollo. Un componente es una cantidad de software
que ofrece un servicio bien definido y
que es reutilizable. Adems debe ser
posible reutilizarlo de verdad: desde
cualquier lenguaje y cualquier sitio.
Cualquiera que tenga conocimiento
sobre cmo funcionan los lenguajes de
programacin a bajo nivel sabr que
esto es muy muy complicado. Por ello
se han desarrollado infraestructuras
que nos permiten interactuar con los
componentes de manera indirecta. A
este software se le suele llamar middleware (algo as como software de
en medio).
Ejemplos famosos de Middleware son
J2EE, que muchos conocern, y
CORBA, que a muchos les gustara no
conocer. Ambos son sistemas enormes
y costosos que relegan al programador
a mera herramienta en manos de ingenieros, denominados arquitectos, que
conocen su compleja infraestructura.

Pero los sistemas de componentes


tambin se emplean en software libre y
han dado buenos resultados. Quizs el
ms desconocido es UNO, de Universal
Network Objects, el sistema que emplea
OpenOffice, ver Listado [1], y que SUN
desarroll para su precursor: StarOffice.

PyUNO
Un sistema de componentes con el que
slo se pueda programar en un
lenguaje no tiene mucha utilidad. Por
eso en OpenOffice se han asegurado de
fomentar la creacin de interfaces a
distintos lenguajes de programacin.
Podemos acceder a UNO usando
Javascript, Java, Ruby, Perl o Python
(ver Recurso [2]).
PyUNO es el nombre de la interfaz y
podremos encontrarlo sin problemas en
nuestra distribucin de Linux. Evidentemente, necesitamos tambin tener
instalado OpenOffice. En este artculo
hemos realizado los programas usando
OpenOffice 2.0, que cambi la interfaz
respecto a la versin 1.0, y la versin
de PyUNO 0.7.0.

Un Ejemplo Rpido
Vamos a crear el famoso Hola mundo
con PyUNO. Para ello primero debemos
arrancar OpenOffice con el siguiente
comando desde el directorio donde est
instalado:
$> ./sofficeU
"-accept=socket,U

PYTHON

23

INTEGRACIN

OpenOffice

Listado 1: Programa Hola Mundo


01 import uno
02
03 localContext = uno.getComponentContext()
04 resolver = localContext.ServiceManager.createInstanceWithContext
(com.sun.star.bridge.UnoUrlResolver, localContext )
05 ctx = resolver.resolve( uno:socket,host=localhost,port=2002;urp;
StarOffice.ComponentContext )
06 desktop = ctx.ServiceManager.createInstanceWithContext(
com.sun.star.frame.Desktop,ctx)
07 doc =
desktop.loadComponentFromURL(private:factory/swriter,_blank,0,())
08 cursor = doc.Text.createTextCursor()
09 doc.Text.insertString( cursor, Hola Mundo, 0 )
10 ctx.ServiceManager

host=localhost,U
port=2002;urp;"

Al arrancar OpenOffice se arranca su


sistema de componentes. Podemos pensar en este proceso como en el
arranque de un servidor, slo cuando
est funcionando podrn los clientes
trabajar con l.
Las opciones que pasamos son para
que se cree un socket y se escuche en
localhost en el puerto 2002. Por defecto

OpenOffice no abre el socket, de manera que no podrn controlar nuestro


OpenOffice sin nuestro consentimiento.
OpenOffice incorpora de serie varios
intrpretes de lenguajes, entre ellos
uno de Python que ya viene preconfigurado para poder hacer uso de la librera UNO. Est junto al resto de ejecutables de OpenOffice, as que lo ejecutaremos desde all. El programa que
usaremos se encuentra en el Listado
[2].

El proceso es el siguiente:
Obtenemos un contexto local (un
sitio donde guardar los datos de la
conexin)
Arrancamos el componente UnoUrlResolver que nos sirve para acceder
a otro OpenOffice en otro equipo
(en nuestro caso accederemos a
nuestro propio equipo)
Emplearemos el objeto resolver para
acceder al OpenOffice remoto
Arrancamos un Desktop (escritorio) de OpenOffice (esto es, una
instancia de OpenOffice vaca)
Arrancamos un SWriter (es decir, el
procesador de textos) en el escritorio
Obtenemos un cursor, con el que
podremos posicionarnos dentro del
texto
e insertamos texto en el cursor
El resultado, no muy espectacular,
podemos verlo en la Figura [1]. Ya tenemos nuestro hola mundo insertado
en SWriter.
Demasiado cdigo? Piensa por un
momento lo que estamos haciendo.

Listado 2: OfficeBroker
01 import uno
02 import random
03 import time
04 import httplib
05 import csv
06
07 class Calc:
08 def __init__(self):
09 self.conecta()
10
11 def conecta (self):
12 self.local = uno.getComponentContext()
13 self.resolver = self.local.ServiceManager.
createInstanceWithContext
(com.sun.star.bridge.UnoUrlResolver, self.local)
14 self.context =
self.resolver.resolve(uno:socket,host=
localhost,port=2002;
urp;StarOffice.ComponentContext)
15 self.desktop = self.context.ServiceManager.
createInstanceWithContext
(com.sun.star.frame.Desktop, self.context)
16 #self.doc = self.desktop.getCurrentComponent()
17 self.doc = self.desktop.loadComponentFromURL
(private:factory/scalc,_blank,0,())
18 self.hojas = self.doc.getSheets()
19 self.s1 = self.hojas.getByIndex(0)
20
21 def actualiza(self, cotizacion, fila):
22
23 i = 0
24 for entrada in cotizacion:
25 if (i == 0) or (i == 2) or (i ==3):
26 self.s1.getCellByPosition(i,fila).

24

PYTHON

27
28

setString(entrada)
else:
self.s1.getCellByPosition(i,fila).setValue
(float(entrada))

29
30 i = i + 1
31
32 def getSimbolo(simbolo):
33 c = httplib.HTTPConnection(finance.yahoo.com)
34 c.request(GET,
/d/quotes.csv?s=+simbolo+&f=sl1d1t1c1ohgv&
e=.csv)
35 r = c.getresponse()
36 cad = r.read()
37 reader = csv.reader([cad])
38 resultado = []
39 for row in reader:
40 resultado = row
41 return resultado
42
43 if __name__ == __main__:
44
45 simbolos = [GOOG,MSFT,RHAT]
46
47 c = Calc()
48
49 while(1):
50 i = 0;
51 for s in simbolos:
52 c.actualiza(getSimbolo(s),i)
53 i = i + 1
54
55 time.sleep(10)

W W W. L I N U X - M A G A Z I N E . E S

OpenOffice

Figura 1: Un documento de Write de OpenOffice con el ineludible


Hello World generado a partir de PyUNO.

Hemos levantado dos componentes y


hecho acceso remoto a otro OpenOffice. Este segundo OpenOffice puede
estar en una mquina al otro lado del
mundo. Es algo bastante impresionante, pero por el momento poco til.
Veamos un poco ms sobre UNO antes
de realizar un programa ms til.

Figura 2: El programa examina los valores de la bolsa NASDAQ de


Yahoo a intervalos regulares y los inserta en una hoja de clculo.

lar ese servicio a algn componente


grfico, como por ejemplo un botn o
men.
Comenzaremos por realizar un programa que funcionar de manera
externa a OpenOffice, y despus crearemos un componente con l y lo integraremos en OpenOffice.

Arquitectura de UNO

Nuestro Programa de Stocks

OpenOffice est implementado en


C++. UNO se usa internamente para
realizar cualquier cosa. Bsicamente,
OpenOffice no es ms que una gran
cantidad de componentes que interactan entre s. Todo dentro de OpenOffice es un componente, as que
podemos acceder a cualquier parte de
la aplicacin, incluso reconstruir
OpenOffice en Python!
Los sistemas de componentes usan
un registro de componentes al que se le
puede pedir que arranque componentes. El registro localiza el componente en disco y lo carga en memoria,
de manera que puede ser usado. Las
llamadas a las funciones no se realizan
directamente, sino que se suele
emplear algn sistema no dependiente
de lenguaje o plataforma, como puede
ser XML o un formato ASCII.
El registro tambin debe ser capaz de
gestionar los recursos que consume el
componente, descargndolo de memoria cuando ya no sea necesario.
Los componentes pueden ser programados en cualquier lenguaje con el que
se tenga interfaz. Un componente es un
conjunto de ficheros que proporcionan
un servicio. Se acompaan de un
fichero XML que describe su funcionalidad. Lo mejor es que podemos vincu-

Comencemos con la parte til, ver Listado [2]. Vamos a crear un sistema que
nos permita controlar las acciones de
una serie de empresas que estn en
bolsa dentro de un ndice tecnolgico,
el Nasdaq (para algo estamos en una
revista de informtica), usando la hoja
de clculo SCalc y un programa Python.
Nuestro programa acceder usando
Internet a un sitio web donde podr
recoger los datos en CSV (Valores Separados por Comas), los procesar y los
introducir en SCalc.
Comenzaremos por crear una funcin que recoja el fichero CSV y lo procese. Lo que hacemos es conectarnos
con la pgina web finance.yahoo.com.
Yahoo tiene un sitio web bastante
avanzado sobre cotizaciones de bolsa,
y uno de sus servicios nos permite
recoger los datos de cotizacin de una
empresa en tiempo real en formato
CSV. Sin embargo, Yahoo no nos permitir acceder a los datos demasiado a
menudo, ya que dispone de un servicio de pago para ello, as que puede
cortarnos el grifo en cualquier
momento si hacemos demasiadas consultas por minuto. Por eso recogeremos los datos cada 10 segundos. La
funcin getSimbolo() se encargar de
ello.

W W W. L I N U X - M A G A Z I N E . E S

INTEGRACIN

Ahora ya tenemos los datos, tenemos


que mandarlos a SCalc. Hemos creado
un objeto llamado Calc para gestionar
el acceso. Hemos metido en el mtodo
constructor (__init__) el cdigo que
conecta con OpenOffice, puesto que
slo lo haremos una vez. Una hoja de
clculo posee varias hojas, as que
tendremos que solicitar una usando el
mtodo getSheets(), que nos devuelve
una lista con las distintas hojas. Dentro
de esta lista usaremos getByIndex()
para seleccionar la primera hoja, que
es la que se ve cuando arrancamos
SCalc.
El mtodo actualiza() admite una
lista con los datos de cotizacin y
nmero que representa la fila donde
aparecer en SCalc. Una hoja de clculo
se compone de celdas, y stas tienen un
tipo. La funcin getCellByPosition() nos
permite acceder a una celda pasndole
la columna y la fila (al revs de lo normal, as que cuidado).
Una vez localizada la celda, tenemos
varias funciones para poder asignar un
valor:
setString(): para poner una cadena
setValue(): para poner un nmero
setFormula(): para poner una frmula
El dato cotizacin es la lista de
parmetros de cotizacin, pero vienen
dados como cadenas de caracteres. Las
posiciones 0, 2 y 3 son realmente cadenas, pero el resto son nmeros. Por eso
tenemos que convertir ciertos valores
al tipo float() mediante la funcin
float().
El resultado se puede ver en la Figura
[2]. Observamos cmo se abre una ventana de SCalc y se rellena con los val-

PYTHON

25

INTEGRACIN

OpenOffice

ores de las contizaciones, adems de


cmo se actualizan cada 10 segundos.
Si creamos un grfico que use esos valores, se actualizar con ellos.
Pero este es un programa externo
estara bien que pudisemos hacer eso
pulsando un botn

Creamos un Componente UNO


Los componentes UNO no son ms que
cdigo debidamente empaquetado. Los

paquetes que OpenOffice admite tienen


una estructura fija. Son ficheros ZIP
que contienen los ficheros con el
cdigo fuente, recursos (como imgenes) y un fichero de configuracin
XML.
Los ficheros deben tener nombres
especiales. El fichero de configuracin
debe llamarse Addons.xcu y permite
asignar el cdigo fuente del paquete
con el widget que deseemos, un botn,

una entrada de un men Ver Listado


[3].
La sintaxis del fichero parece bastante complicada, cuando en realidad
no es muy difcil de entender. Bsicamente decimos que queremos que nuestro componente se asocie con una
entrada en el men Addons que est
en Tools o Herramientas en castellano.
Nuestro componente tiene una ruta que
especificaremos despus y que es:

Listado 3: Addons.xcu
01 <?xml version=1.0 encoding=UTF-8?>
02 <oor:node
xmlns:oor=http://openoffice.org/2001/registry
03 xmlns:xs=http://www.w3.org/2001/XMLSchema
04 oor:name=Addons
oor:package=org.openoffice.Office>
05 <node oor:name=AddonUI>
06
07 <node oor:name=AddonMenu>
08 <node oor:name=
org.openoffice.comp.pyuno.linuxmagazine.Stock
oor:op=replace>
09 <prop oor:name=URL oor:type=xs:string>
10 <value>service:org.openoffice.comp.pyuno.
linuxmagazine.Stock?insert</value>
11 </prop>
12 <prop oor:name=Title oor:type=xs:string>

13
14
15
16
17
18
19
20

<value/>
<value xml:lang=en-US>Stock Market</value>
<value xml:lang=es>Cotizacin en Bolsa</value>
</prop>
<prop oor:name=Target oor:type=xs:string>
<value>_self</value>
</prop>
<prop oor:name=ImageIdentifier
oor:type=xs:string>
21 <value>private:image/3216</value>
22 </prop>
23
24 </node>
25 </node>
26 </node>
27 </oor:node>

Listado 4: stock_comp.py
01 import uno
02 import unohelper
03
04 import random
05 import time
06 import httplib
07 import csv
08
09 from com.sun.star.task import XJobExecutor
10
11 def getSimbolo(simbolo):
12 c = httplib.HTTPConnection (finance.yahoo.com)
13 c.request(GET,/d/quotes.csv?s=+simbolo+
&f=sl1d1t1c1ohgv&e=.csv)
14 r = c.getresponse()
15 cad = r.read()
16 reader = csv.reader([cad])
17 resultado = []
18 for row in reader:
19 resultado = row
20 return resultado
21
22 class StockJob( unohelper.Base, XJobExecutor ):
23 def __init__( self, ctx ):
24
25 self.ctx = ctx
26
27 def trigger( self, args ):
28 desktop =
self.ctx.ServiceManager.createInstanceWithContext
(com.sun.star.frame.Desktop, self.ctx )
29

26

PYTHON

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

model = desktop.getCurrentComponent()
self.hojas = model.getSheets()
self.s1 = self.hojas.getByIndex(0)
simbolos = [GOOG,MSFT,RHAT]
i = 0;
for s in simbolos:
self.actualiza(getSimbolo(s),i)
i=i+1

def actualiza(self, cotizacion, fila):


i=0
for entrada in cotizacion:
if (i == 0) or (i == 2) or (i ==3):
self.s1.getCellByPosition(i,fila).setString
(entrada)
48 else:
49 self.s1.getCellByPosition(i,fila).setValue
(float(entrada))
50
51 i = i + 1
52
53 g_ImplementationHelper =
unohelper.ImplementationHelper()
54
55 g_ImplementationHelper.addImplementation( StockJob,
org.openoffice.comp.pyuno.linuxmagazine.Stock,
(com.sun.star.task.Job,),)

W W W. L I N U X - M A G A Z I N E . E S

OpenOffice

org.openoffice.comp.pyuno.U
linuxmagazine.Stock

Esta ruta la hemos creado nosotros y


tenemos que tener cuidado de que sea
nica, por eso hemos incorporado linuxmagazine en ella ;). Definimos un
ttulo, que puede estar en varios
idiomas, y una imagen, que hemos
escogido de entre las que proporciona
OpenOffice.
El fichero con el cdigo fuente Python
en s se puede ver en el Listado 4. Tenemos un objeto llamado StockJob que
ser el que se invocar en caso de pulsar la entrada en el men. Ese objeto se
vincula a la ruta que vimos antes. Cada
vez que se pulse sobre la entrada del
men se ejecutar el mtodo trigger,
que descargar de Internet las cotizaciones y las mostrar en la hoja de clculo. Es posible hacer que slo se
muestre el men cuando arrancamos la
hoja de clculo SCalc, pero por motivos
de espacio no hemos puesto la restriccin. An as, si no estamos en una
hoja de clculo no suceder nada, simplemente no funcionar.

Manejo de Paquetes en OpenOffice


Ahora tenemos que generar nuestro
paquete UNO. Para ello necesitaremos
el programa ZIP, gzip no nos vale, y
crear un fichero:
$> zip stock.zipU
stock_comp.py Addons.xcu
updating: ...../Addons.xcuU
(deflated 59%)
updating: ...../stock_comp.pyU
(deflated 57%)
>

Este fichero debe ser integrado en


OpenOffice, iremos al directorio donde
est instalado y ejecutaremos como
root:
$> sudo ./unopkg addU
stock.zip
>

Con esto concluye la instalacin del


paqueteno ha sido tan difcil!
Cuando arranquemos de nuevo
OpenOffice podremos seleccionar la
hoja de clculo SCalc, y en el men

INTEGRACIN

Tools/Herramientas, veremos cmo ha


aparecido al final un nuevo submen:
Complementos (add-ons). Dentro
del mismo aparecer una nueva
entrada llamada Cotizacin de
Bolsa. Si la pulsamos aparecen los
datos de 3 compaas (Google,
Microsoft y Redhat) del Nasdaq en
nuestra hoja de clculo.

Conclusin
Python nos permite un uso nuevo de
algo tan trillado como puede ser un
paquete ofimtico. OpenOffice entero
es accesible desde Python; no es difcil
imaginarse programas que podran
facilitarnos mucho la vida y no son tan
difciles de crear gracias a PyUNO. No
hemos explorado la posibilidad de
actuar sobre un OpenOffice remoto por
falta de espacio, pero es una nueva
opcin que abre un camino para aplicaciones muy interesantes, como puede
ser la edicin distribuida de documentos o un uso ms creativo de la hoja de
clculo.
Todo un mundo de posibilidades se
abre ante nosotros gracias a Python.

INTEGRACIN

Java

on
Yv
ne
Les
s123
com
RF.

No has encontrado tu lector RSS? Hazte uno t mismo con Jython.

Cuando los Mundos Chocan


Os descubrimos Jython, la forma mas sencilla de desarrollar vuestras aplicaciones Java como si las programrais con Python. Por Pedro Orantes y Jos Mara Ruz

Muchos os preguntaris qu es Jython.


Bien, empecemos desde el principio. Un
grupo de programadores, Jim Hugunim
(su creador) y Guido van Rossum (personalidad dentro del mundo de Python)
entre otros, decidieron que la simpleza y
la limpieza del cdigo de Python hara
que programar en Java fuera perfecto,
as que se pusieron manos a la obra y
crearon Jython. Jython es la implementacin de Python en la plataforma Java,
combinando la potencia de los paquetes
de Java, como JavaBeans, con la facilidad y rapidez de desarrollo de aplicaciones de Python. Recordad que desarrollar
una aplicacin es por lo menos dos veces
ms corto en Python que en Java.
Jython posee las mismas caractersticas de Python y adems posee la caracterstica de poder trabajar con las libreras de Java, de forma que, por ejemplo,
podemos disponer del bonito swing de
Java o utilizar JavaBeans e incluso programar applets. En la Web de Jython en
[1] aparecen algunos ejemplos de
applets desarrollados en Jython. Esto
hace de l un lenguaje muy potente, ya
que nos ahorramos tiempo, lneas de
cdigo y resulta menos engorroso de leer
que Java. Incluso podemos compilar el
cdigo para no tener la necesidad de
instalar Jython antes de ejecutar nuestra
aplicacin en cualquier ordenador, ya

28

PYTHON

que es capaz de generar Bytecode Java


(aunque s necesitamos tener Java,
obviamente). Aunque ciertamente no
todo es positivo. Las aplicaciones desarrolladas con Jython suelen ser bastante
ms lentas que las desarrolladas con
Java, algo de lo que se quejan muchos
programadores, pero an as, la potencia
y rapidez de los ordenadores de hoy hace
que apenas se note la diferencia.
Muchos os preguntareis si hace falta
saber Java para programar Jython. En
principio, no. S es conveniente tener

idea de su funcionamiento y disponer de


la API de Java, disponible en la web de
Sun [2], para saber qu queremos hacer
y cmo lo queremos hacer, pero la sintaxis y la forma de programar es muy
diferente. Programar en Java sin la API
de Sun resulta, en la mayor parte de los
casos, imposible. En cambio, para programar en Jython s es necesario saber
programar en Python, as pues, damos
por sabido todo lo que hayis aprendido
en los artculos anteriores de esta
revista.

Listado 1: Tres Botones


01 import javax.swing as swing
02 import java.awt as awt
03
04 cuadroTexto =
swing.JTextField(10)
05
06 def __init__():
07 win = swing.JFrame(Ejemplo con
botones)
08 acciones = [uno, dos,
tres]
09
10 pnlBotones =
swing.JPanel(awt.FlowLayout())
11
12 pnlBotones.add(cuadroTexto)
13 for cadaBoton in acciones:
14 pnlBotones.add (swing.JButton(cadaBoton,actionPerformed=a
ccion))

15
16 win.contentPane.add(pnlBotones)
17 win.size=(300,300)
18 win.pack()
19 win.show()
20
21 def accion(event):
22 accionBoton = event.getActionCommand()
23 if accionBoton == uno:
24 cuadroTexto.setText(UNO)
25 elif accionBoton == dos:
26 cuadroTexto.setText(DOS)
27 else:
28 cuadroTexto.setText(TRES)
29 root = __init__()

W W W. L I N U X - M A G A Z I N E . E S

Java

Figura 1: Nuestra utilidad busca-palabras en marcha.

En este artculo aprenderemos a usar


elementos bsicos de Java en Jython,
trabajaremos con swing, y usaremos
algunos objetos de Java como son los

vectores, entre otras cosas. Para el ejemplo de este artculo nos valdremos de
una aplicacin externa ligeramente
modificada (el cdigo original lo encontraris en la web de xml.com en [3])
desarrollada en Python, para que podis
echarle un ojo al cdigo si os apetece,
desde la cual parsearemos el xml de un
documento RSS para leer las ltimas
noticias de los blogs que ms frecuentemente visitemos. Ms adelante explicaremos esto detalladamente.

Instalacin de Jython
Para trabajar con Jython, necesitamos
tener Java instalado en nuestro orde-

INTEGRACIN

nador. Podemos usar el Java Runtime


Edition (j2re) o el Java Developers
Kit(j2sdk), en su version 1.4.2 como
mnimo, descargables desde [2]. Adems
necesitamos instalar el intrprete de
Jython disponible en [1], en su ltima
versin estable (la jython-2.1) y, por
ltimo, para ejecutar nuestra aplicacin,
deberemos tener instalado el intrprete
de Python (versin 2.3).
Una vez hemos descargado el intrprete de Jython, debemos proceder a la
instalacin del mismo. Ejecutamos java
jython-21 y nos saldr el instalador
(Figura 1), que nos pedir confirmar una
serie de opciones y un directorio, y ya

Listado 2: JyRSS.py
001 #!/usr/bin/jython
002
003 import javax.swing as swing
004 import java.lang as lang
005 import java.awt as awt
006 import java.util as util
007 import os
008
009 class Lector:
010 def exit(self, event):
011 lang.System.exit(0)
012
013 def __init__(self):
014
015 self.vectorrss = util.Vector()
016 self.vectorurl = util.Vector()
017 self.listaRSS()
018 self.listaNoticias()
019 self.pnlBotones()
020 self.menu()
021 if os.path.exists(listarss.txt):
022 self.leeFicheroRss()
023 self.win = swing.JFrame(JyRss, size=(300,
300),windowClosing=self.exit)
024 self.win.setJMenuBar(self.menu)
025 self.win.contentPane.add(self.pnlBoton,awt.BorderLayout.NORTH)
026 self.win.contentPane.add(self.jscplista, awt.BorderLayout.WEST)
027 self.win.contentPane.add(self.jscpNoticias,
awt.BorderLayout.CENTER)
028 self.win.setSize(600, 400)
029 self.win.show()
030
031 def pnlBotones(self):
032 self.pnlBoton = swing.JPanel(awt.FlowLayout())
033 acciones = [Agregar,Borrar,Leer]
034 self.txtUrl = swing.JTextField(10)
035 lblNombre = swing.JLabel(Nombre)
036 self.txtNombre = swing.JTextField(10)
037 lblUrl = swing.JLabel(Url)
038 self.pnlBoton.add(lblNombre)
039 self.pnlBoton.add(self.txtNombre)
040 self.pnlBoton.add(lblUrl)
041 self.pnlBoton.add(self.txtUrl)

W W W. L I N U X - M A G A Z I N E . E S

042
043 for cadaBoton in acciones:
044 self.pnlBoton.add(swing.JButton(cadaBoton,
actionPerformed=self.accionMenu))
045
046 def menu(self):
047 opciones = [Guardar]
048 self.menu = swing.JMenuBar()
049 archivo = swing.JMenu(Archivo)
050 for eachOpcion in opciones:
051 archivo.add(swing.JMenuItem(eachOpcion, actionPerformed=self.accionMenu))
052 self.menu.add(archivo)
053
054 def listaRSS(self):
055 self.lstLista = swing.JList()
056 self.jscplista = swing.JScrollPane(self.lstLista)
057 self.jscplista.setSize(100,100)
058
059 def listaNoticias(self):
060 self.lstNoticias = swing.JEditorPane()
061 self.jscpNoticias = swing.JScrollPane(self.lstNoticias)
062
063 def leeFicheroRss(self):
064 f = open(listarss.txt,r)
065 fu = open(listaurl.txt, r)
066 linea = f.readline()
067 lurl = fu.readline()
068 while linea:
069 self.vectorrss.add(linea)
070 self.vectorurl.add(lurl)
071 linea = f.readline()
072 lurl = fu.readline()
073 f.close()
074 fu.close()
075 self.lstLista.setListData(self.vectorrss)
076
077 def leeFicheroNoticias(self):
078 fg = open(news.txt,r)
079 texto = fg.read()
080 fg.close()
081 self.lstNoticias.setText(texto)
082
083 def guardarFichero(self):

WWW.LINUX- MAGAZINE.ES

PYTHON

29

INTEGRACIN

Java

Figura 2: Ejemplo con botones Swing.

tendremos Jython instalado en nuestro


ordenador. Adicionalmente podemos
enlazar los ejecutables de Jython a nuestro directorio de binarios del sistema
(ln -s jython-2.1/jython /usr/bin/jython y
ln -s jython-2.1/jythonc /usr/bin/jythonc
en nuestro caso) para no tener que ejecutar los binarios desde el directorio
donde lo tengamos instalado. Esto ltimo es recomendable, adems de que
resulta mucho ms cmodo.

Primeros Pasos
Bueno, ya est todo preparado en nuestro sistema. Es hora de ver cmo funciona Jython. Para empezar, podis
trastear un poco con el intrprete como
lo habis hecho con el de Python, y as
podris ver que el funcionamiento es
idntico.
$ jython
Jython 2.1 on java1.4.2_05U
(JIT: null)

Type "copyright", "credits" orU


"license" for more information.
>>> print 'Hola Mundo'
Hola Mundo
>>>

Bien, vamos a empezar a ver algunos


ejemplitos en java. En primer lugar, qu
tal el tpico Hola mundo con swing? Este
ejemplo mostrar una ventana llamada
Hola Mundo, con un cuadro de texto.
Para cerrarla tendris que cerrar el intrprete con Control + C, ya que en el
ejemplo no implementamos la salida de
la aplicacin.
01 $ jython
02 Jython 2.1 on java1.4.2_05
(JIT: null)
03 Type "copyright", "credits"
or "license"
for more information.
04 >>> import javax.swing as
swing
05 >>> win = swing.JFrame("Hola
mundo")
06 >>> texto =
swing.JLabel("Hola
mundo")

Listado 2: JyRSS.py (Cont.)


084 fg = open(listarss.txt,w)
085 furl =
open(listaurl.txt,w)
086 j = self.vectorrss.size()
087 i = 0
088 while i<=j-1:
089 texto = self.vectorrss.get(i)
090 fg.write(texto +\n)
091 texto = self.vectorurl.get(i)
092 furl.write(texto +\n)
093 i = i+1
094 fg.close()
095 furl.close()
096
097 def accionMenu(self, event):
098 self.accion = event.getActionCommand()
099 if self.accion == Agregar:
100 if self.txtNombre.getText()
== :
101 self.vectorrss.add(SIN NOMBRE\n)
102 else:
103 self.vectorrss.add
(self.txtNombre.getText())
104 if self.txtUrl.getText() ==
:
105 self.vectorurl.add(SIN
URL\n)
106 else:
107 self.vectorurl.add
(self.txtUrl.getText())

30

PYTHON

108
109

self.lstLista.setListData
(self.vectorrss)
110 self.txtNombre.setText()
111 self.txtUrl.setText()
112
113 elif self.accion == Leer:
114 item = self.lstLista.getSelectedIndex()
115 url =
self.vectorurl.get(item)
116 os.system(python lrss.py +
url)
117 self.leeFicheroNoticias()
118
119 elif self.accion == Borrar:
120 itemborrar =
self.lstLista.getSelectedIndex()
121 self.vectorrss.remove(itemborrar)
122 self.vectorurl.remove(itemborrar)
123 self.lstLista.setListData
(self.vectorrss)
124
125 elif self.accion == Guardar:
126 self.guardarFichero()
127
128 root = Lector()

Figura 3: Buscador desde la lnea de comandos.

07 >>>
win.contentPane.add(texto)
08 >>> win.pack()
09 >>> win.show()
10 >>>

Como podis ver resulta muy sencillo, a


la vez de que swing resulta muy agradable para la vista. Java incluye tambin
un sistema para cambiar la apariencia de
la interfaz por si no os gusta la que
arranca por defecto.

Programacin
Al igual que en Python, para nuestras
aplicaciones lo mas cmodo es hacer
que el intrprete ejecute un fichero (o
varios) de cdigo. Jython no tiene
ningn tipo de extensin establecida
para los ficheros, y lo ms normal es
usar la misma que Python .py para tener
nuestros ficheros de cdigo bien diferenciados de los dems.
Para usar las libreras de Java, lo
primero que tenemos que hacer es
importarlas al estilo de siempre de
Python. Como hemos visto en el ejemplo
de antes, para importar las libreras de
swing basta con importar lo que necesitemos. Imaginad que necesitamos la
clase Vector, que est dentro del paquete
de utilidades, bien podemos hacer
import java.util as util o bien directamente import java.util.vector as vector, y
ya tendremos acceso a esa clase. Para
utilizarla bastara con llamar a su constructor (os recuerdo que tengis siempre
a mano la documentacin del API de
Java) con l, crearemos una ocurrencia
vector (en este caso), pepe = util.vector() y una vez creada, podemos acceder
a cualquiera de sus mtodos directamente en la ocurrencia que acabamos de
crear, pepe.add(Hola) que, para los
que no estn muy familiarizados con
Java, aade un objeto de tipo String
(cadena de caracteres) al vector.
Cada ocurrencia de Java tiene el
mismo funcionamiento en Jython. Es
importante que tengis esto en cuenta.

W W W. L I N U X - M A G A Z I N E . E S

Ahora vamos a mezclar un poco de cada (Figura 2). Para


ello voy a crear tres botones, a cada uno lo llamaremos de
una forma distinta y les aadiremos un listener (para coger
los eventos que produce cada botn), de forma que al pulsar en cada uno, se escriba su nombre en un cuadro de
texto. Veamos cmo se hace esto en el Listado 1.
Es sencillo verdad?, si os dais cuenta, para crear los
botones he usado cdigo propio de Python usando una lista
con los nombres de los botones y crendolos con un bucle
for, dentro del cual llamo al constructor de JButton, pasndole unos argumentos, y aadindolos en el panel mediante
el mtodo .add() que implementa JPanel. Bueno, espero
que os haya gustado el ejemplo, porque ahora viene nuestra
aplicacin en serio.

Lector de Noticias RSS -JyRSSPuede que algunos de vosotros os preguntis qu es RSS.


RSS no es ms que un fichero .xml con una serie de etiquetas. stas siguen un estndar definido por xml.com para
transmitir pequeas noticias, por ejemplo, a travs de
Internet, de forma que no haga falta abrir un navegador
para leerlas, sino que slo bastara con usar una aplicacin
que descargue y prepare la noticia para que la podamos
leer, como hace nuestra aplicacin JyRSS.
Para empezar, necesitamos el cdigo que hemos mencionado al principio del artculo. Este cdigo ser el encargado de descargar y parsear el fichero RSS con las noticias,
de forma que guardar en un fichero el contenido ya
parseado del documento RSS para que nuestra aplicacin
pueda cargar su contenido dentro de un cuadro de texto.
Tiene algunas limitaciones, y es que no soporta todas las
codificaciones de texto, por lo que os recomiendo que cuando lo probis, usis direcciones extranjeras, a ser posible en
ingls, como Slashdot.com (http://www.slashdot.com/
index.rss), ya que con algunos blogs en castellano, como
barrapunto.com, no funciona correctamente debido a una
serie de caracteres contenidos en el documento.
JyRSS constar de varias partes. Un JFrame (ver Listado
1) donde irn embebidos el Panel de botones y los cuadros
de texto. Uno de los cuadros de texto es un JList, donde
mostraremos los nombres de los sitios que contienen las
noticias, y el otro un JEditorPane, donde se mostrarn las
noticias. Adems ambos hacen uso de JScrollPane para que
puedan hacer scroll en caso de que haya texto que ocupe
toda la pantalla.
Tambin hacemos uso del JMenu y de JMenuBar para
crear el pequeo men archivo, donde est la opcin de
guardar, que guardar en dos ficheros (uno con los nombres de los sitios y otro con las urls de cada sitio) todos los
sitios web que le hayamos aadido. Este men no es nada
necesario, pero queramos incluirlo para que virais lo fcil
que es crearlo.
El panel de botones tendr tres JButtons, uno para aadir
una nueva url a la lista, uno para borrar una url de la lista y
otro que lanzar el pequeo parser de RSS (lrss.py) y nos
mostrar las noticias de cada sitio que aparecern en el
JEditorPane.
Tambin se hace uso de la clase Vector. Usaremos dos
vectores, uno para guardar los nombres y otro para las

W W W. L I N U X - M A G A Z I N E . E S

INTEGRACIN

Java

direcciones. El vector de nombres se lo


pasaremos a la ocurrencia JList para
que aada todos los nombres de los
sitios en pantalla. Usaremos el paquete
java.lang para implementar la funcin
de salir de la ventana al pulsar la x de
nuestra aplicacin (no se implementa
por defecto).
De Python, haremos uso del paquete
os, desde el que nos valdremos de
os.path.exists() para comprobar si existe
el fichero de nombres al arrancar la aplicacin y de os.system(), que ejecutar el
script Python que leer las noticias. ste
guardar las noticias en un fichero, que
luego leer nuestra aplicacin. No os
preocupis si veis que tarda un poco, eso
es por dos motivos, tiene que descargar
el fichero de Internet, y adems tiene
que parsearlo, y las libreras que utiliza
(minidom)
son
bastante
lentas.
Usaremos tambin las utilidades de
escritura y lectura de ficheros (open(),
write(), read(), etc) para manejarnos
con ellos. Podemos ver el resultado en la
Figura 3.
Como vis, programar java con
Jython no resulta para nada difcil, al
contrario, resulta muy cmodo, y desde
luego mucho mas sencillo que Java. En
el Listado 3 encontraris el cdigo
Python de la pequea aplicacin externa.

Pongo los nombres de las clases y


paquetes de Java que se van a usar, para
que os vayan sonando cuando vayis
leyendo el cdigo. As podris buscar
ms rpido en la API de Java y no tendris problemas para encontrarlas.
Vuelvo a hacer hincapi en que es necesario que lo tengis.

Mejoras para JyRSS


Es obvio que a este cdigo le faltan
muchas cosas, adems de que debe tener
varios bugs, como por ejemplo que al
pulsar el botn Leer cuando no hay
ninguna url y/o ningn nombre, Java
lanza una excepcin. Os animamos a que
lo depuris.
Os sugerimos que le hagis algunas
mejoras, ya que de esta manera os
servir para practicar con el cdigo que
os dejamos. Por ejemplo, hacer que el
cuadro JEditorPane os cambie la apariencia del texto (negrita, cursiva, etc),
que cada vez que pulsis sobre un nombre en la JList lea la noticia directamente
(deberis trabajar con el listener de
JList), aadir la opcin de guardar las
noticias del sitio que ms os gusten,
etc

Entornos de Programacin Jython


Mucha gente prefiere trabajar con IDEs
(interfaz para el desarrollo de aplica-

ciones) a la hora de programar.


Actualmente no he encontrado an
ninguno que sea exclusivamente para
desarrollar en Jython. Lo que s existe,
son plugins que instalamos en otros
IDEs y que nos permiten trabajar con
este lenguaje. Podemos encontrar diferentes plugins para dos de los IDEs ms
populares, uno para Netbeans (ver [4]),
que os podis bajar desde la aplicacin
de actualizacin que lleva implementada. Y luego tenis otro para Eclipse (ver
[5]) llamado Red Robin, que podis
encontrar en [6]. Tanto en la web de
Netbeans como en la web de Red
Robin, se explica cmo debemos insta
larlos.

Recursos
[1] Pgina de Jython:
http://www.jython.org
[2] Descarga de Java:
http://java.sun.com
[3] Cdigo original del programa:
http://www.xml.com/lpt/a/2002/12/18/
dive-into-xml.html
[4] Plugins de Netbean:
http://www.netbeans.org
[5] IDE Eclipse:
http://www.eclipse.org
[6] Plugin Jython para Eclipse:
http://home.tiscali.be/redrobin/jython/

Listado 3: lrss.py
01 from xml.dom import minidom
02 import urllib
03
04 DEFAULT_NAMESPACES = \
05 (None, # RSS 0.91, 0.92, 0.93, 0.94, 2.0
06 http://purl.org/rss/1.0/, # RSS 1.0
07 http://my.netscape.com/rdf/simple/0.9/ # RSS 0.90
08 )
09 DUBLIN_CORE = (http://purl.org/dc/elements/1.1/,)
10
11 def load(rssURL):
12 return minidom.parse(urllib.urlopen(rssURL))
13
14 def getElementsByTagName(node, tagName, possibleNamespaces=DEFAULT_NAMESPACES):
15 for namespace in possibleNamespaces:
16 children = node.getElementsByTagNameNS(namespace,
tagName)
17 if len(children): return children
18 return []
19
20 def first(node, tagName,
possibleNamespaces=DEFAULT_NAMESPACES):
21 children = getElementsByTagName(node, tagName, possibleNamespaces)
22 return len(children) and children[0] or None

32

PYTHON

23
24 def textOf(node):
25 return node and .join([child.data for child in
node.childNodes]) or
26
27 if __name__ == __main__:
28 import sys
29 rssDocument = load(sys.argv[1])
30 fn = open(news.txt,w)
31 Noticia=
32 for item in getElementsByTagName(rssDocument,
item):
33 Noticia = Title: __ + textOf(first(item,
title))+ __\n
34 Noticia = Noticia + Link: \n + textOf(first(item,
link))+ \n
35 Noticia = Noticia + Description: \n\n +
textOf(first(item, description))+ \n
36 Noticia = Noticia + \nDate: + textOf(first(item,
date, DUBLIN_CORE))+ \n
37 Noticia = Noticia + \nAuthor: + textOf(first(item,
creator, DUBLIN_CORE))+ \n
38 Noticia = Noticia +
---------------------------------------\n
39 fn.write(Noticia)
40 fn.close()

W W W. L I N U X - M A G A Z I N E . E S

Ajax

INTEGRACIN

La nueva tecnologa web.

Limpieza
Total
AJAX es la palabra de moda, Google usa AJAX, Yahoo usa
AJAX todo el mundo quiere usar AJAX pero lo usas t? y
ms importante an qu demonios es AJAX?
Por Jos Mara Ruz y Pedro Orantes

Gunnar Pippel - 123RF.com

A Brave New World (Un Mundo


Feliz) es el nombre de la famosa
novela de Aldous Huxley, en ella nos
muestra un mundo distinto y aterrador
pero que pareca, y parece, cada vez
ms cercano.
Nosotros no tenemos una visin tan
pesimista del mundo, pero es probable
que ese ttulo (que se podra traducir
literalmente por un nuevo y desafiante
mundo) explique todo el revuelo que
est levantando AJAX. El trmino fue
acuado por Jesse James Garrett en el
artculo [1] del cuadro de Recursos.
Durante mucho tiempo, las GUIs, las
Interfaces Grficas de Usuario, han
dominado la informtica. La gente que
trabajaba en la Web siempre estaba
intentando convencer a todo el mundo
de que para la mayora de los programas,
una interfaz web bastaba. Pero los usuarios estaban acostumbrados a ciertas
caractersticas, como el auto-completado

W W W. L I N U X - M A G A Z I N E . E S

de campos o el arrastrar y soltar, que


eran imposibles en la Web.
Conforme avanzaba el tiempo, numerosas empresas y personas proponan
soluciones. La lista es interminable:
JavaScript, Java Applets, ActiveX, Tcl,
VBScript, Macromedia Flash
Pero todas fallaban de uno u otra
manera. En el caso de Java, para ejecutar
el Applet necesitabas tener instalado el
Java Runtime Environment, y la mayora
de los usuarios no saban ni qu era
aquello que se le peda. Lo mismo ocurra con Flash.
Lo peor era que cuando estaba solucionado el tema de la instalacin del
software adecuado, los desarrolladores
creaban, y crean, pginas horribles llenas de cosas movindose que distraen e
irritan. Se sentan impulsados a usar
hasta la ltima capacidad de las nuevas
herramientas y acababan generando
monstruosidades.

Esta fase ya casi ha pasado y ahora se


busca la sencillez, y en el momento justo
surgi AJAX. Para ms informacin ver
url [2] del cudro de Recursos.

Pero Qu es AJAX?
Muy buena pregunta. Lo cierto es que
AJAX ha estado delante de nuestras narices todo el tiempo, esperando a que
alguna mente despierta lo redescubriese.
El acrnimo AJAX se compone de las
palabras Asynchronous JavaScript and
XML, trmino acuado por Jesse James
Garrett, y curiosamente su existencia se
debe a una de esas famosas violaciones
de los estndares que suele realizar
Microsoft con sus productos.
All por 1998, Microsoft introdujo dentro de sus productos una librera que le
permita hacer consultas usando el protocolo HTTP de manera autnoma y
asncrona. Cuando tu navegador accede
a una pgina y sta contiene cdigo

PYTHON

33

INTEGRACIN

Ajax

Javascript, este cdigo a su vez puede


traer informacin de esa u otras pginas
de manera independiente. Si adems se
hace que este cdigo permanezca en ejecucin respondiendo a eventos, tenemos
entre manos la posibilidad de traer informacin al navegador sin recargar la pgina.
Esto es til para algunas tareas, pero
no demasiado, ya que a nuestro puzzle
le faltan piezas. La primera pieza es la
adopcin de esta librera por casi todos
los navegadores, por lo tanto el cdigo
pasa a ser de aplicacin universal.
Adems resulta que podemos modificar el contenido de la pgina en tiempo
real usando el denominado rbol DOM.
Y por si fuese poco, cuando AJAX fue
definido, los programadores comenzaron
a usar protocolos XML para comunicarse
con los servidores.
Qu quiere decir esto? Pues que
ahora, con AJAX, podemos cargar una
pgina y, sin tener que recargarla, traernos informacin, modificar la pgina en
tiempo real e interactuar con servidores
remotos usando protocolos XML.
Bsicamente, una vez cargada la pgina web tenemos entre manos todas las
posibilidades de programacin de una

Figura 1: Esquema de nuestra aplicacin AJAX con todos sus compenentes.

GUI tradicional. Y todo esto sin necesidad de plugins ni instalaciones; toda esta
tecnologa est en nuestros navegadores
esperando ser usada.

Cmo Encaja Python?


Pues vamos a realizar un pequeo servidor de contenidos en Python que pueda
ser consultado usando AJAX. Crearemos
una web con algo de cdigo Javascript,
que a intervalos acceder a nuestro servidor Python y modificar el aspecto de
la pgina web.

Los 5 Ingredientes
Los cinco ingredientes necesarios para
elaborar nuestro producto son CSS,
Javascript, HTML, XML y Python, como
aparecen en la figura 1, y cada uno tiene
su funcin en esta obra.
HTML es la base sobre la que vamos a
trabajar, y as definimos una pgina web
en la que todo ocurrir. De hecho, con el
paso del tiempo el propio HTML ha acabado convirtindose en una especie de
plantilla donde campan a sus anchas
CSS y Javascript.

Listado 1: server.py
01 #!/usr/local/bin/python
02
03 import BaseHTTPServer
04 import os
05 import cgi
06
07 class AJAXHTTPRequestHandler (BaseHTTPServer.
BaseHTTPRequestHandler):
08
09 Responde a peticiones HTTP
10
11 def do_GET(self):
12 Gestiona los GET
13
14 acciones = {
15 / : [envia_fichero,index.html],
16 /ps.xml : [envia_comando, ps afx],
17 /df.xml: [envia_comando, df],
18 /who.xml: [envia_comando,who],
19 /uname.xml: [envia_comando,uname -a]}
20
21 if self.path in acciones.keys():
22 accion = acciones[self.path]
23 (getattr(self,accion[0]))(self.path,accion[1])
24 else:
25 if (self.path[-3:] == .js or
26 self.path[-4:] == .css):
27 self.envia_fichero(,self.path[1:])
28

34

PYTHON

29 else:
30 self.envia_fichero(,404.html)
31
32 def envia_fichero(self,ruta,fichero):
33 # No usamos ruta, pero as simplificamos el cdigo
34 p = Pagina(fichero)
35 self.enviar_respuesta(p.tipo(), p.contenido())
36
37 def envia_comando(self,ruta,comando):
38 c = Comando(comando)
39 self.enviar_respuesta(c.tipo(), c.contenido())
40
41 def enviar_respuesta(self, tipo, contenido):
42 self.enviar_cabecera(tipo)
43 self.wfile.write(contenido)
44
45 def enviar_cabecera(self, tipo):
46 self.send_response(200)
47 self.send_header(Content-type,text/ + tipo)
48 self.end_headers()
49
50 class Pagina:
51 def __init__(self,nombre):
52 self.nombre = nombre
53 self.texto =
54 fichero = file(self.nombre)
55 self.texto = fichero.read()
56 fichero.close()
57

W W W. L I N U X - M A G A Z I N E . E S

Ajax

INTEGRACIN

Listado 1: server.py (Cont.)


58 def contenido(self):
59 return self.texto
60
61 def tipo(self):
62 tipo = html
63 ext = self.nombre[-4:]
64 if (ext == html):
65 tipo = html
66 elif (ext == .xml):
67 tipo = xml
68 elif (ext == .css):
69 tipo = css
70 return tipo
71
72 class Comando:
73 def __init__(self,comando):
74 self.tuberia = os.popen(comando)
75 self.xml =
76
77 def contenido(self):
78 # fichero XML

CSS nos permite otorgar propiedades


visuales a los elementos de HTML.
Javascript es el encargado de actuar
en la mquina cliente, en el navegador,
y puede modificar tanto el HTML como
las propiedades visuales que CSS define. Con la llamada XMLHttpResponse
sus atribuciones se han disparado.
Ahora se ve como un lenguaje de programacin de pleno derecho. En los
prximos aos puede que adquiera
mucha ms importancia de la que ha
tenido hasta ahora.
XML es el nuevo lenguaje estndar de
intercambio
de
informacin.
Prcticamente cualquier lenguaje dispone ya de libreras para generar y analizar
documentos XML. Dentro del mundillo,
AJAX se ha convertido en el estndar
para el intercambio de informacin con
el servidor y para la serializacin de
objetos con protocolos como JSON.
Y, como no, Python. En nuestro caso
se va a encargar tanto de realizar las
tareas de servidor HTTP, como de recolectar informacin importante y confeccionar con ella ficheros XML.
Tenemos que compenetrar todos estos
elementos para realizar nuestro proyecto. El objetivo es que los componentes
HTML, Javascript, CSS y XML sean tan
estticos como sea posible, debido a que
todos ellos interactan con el usuario.
Es en el servidor Python donde debemos aportar la flexibilidad necesaria
como para aadir nuevas caractersticas

W W W. L I N U X - M A G A Z I N E . E S

79 if not self.xml:
80 self.xml = <?xml version=\1.0\ ?>
81 self.xml += <salida>
82 linea = self.tuberia.readline()[:-1] # para quitar
el \n
83 while linea:
84 self.xml += <linea> + cgi.escape(linea) +
</linea>
85 linea = self.tuberia.readline()[:-1]
86 self.xml += </salida>
87 return self.xml
88
89 def tipo(self):
90 return xml
91
92 def test(HandlerClass = AJAXHTTPRequestHandler,
93 ServerClass = BaseHTTPServer.HTTPServer):
94 BaseHTTPServer.test(HandlerClass, ServerClass)
95
96 if __name__ == __main__:
97 test()

sin tener que cambiar ninguno de los


otros elementos.

La Parte de Python:
BaseHTTPRequest
Python dispone en sus libreras estndar
de muchos esqueletos para distintos
tipos de servidores. Entre ellos encontramos BaseHttpRequest. Esta clase nos
permite construir servidores HTTP sin
excesivo esfuerzo, as que la emplearemos.
Pero no debemos usarla directamente,
sino a travs de la clase BaseHttp
RequestHandler, que es la encargada de
gestionar los eventos que se suceden en
el servidor. Por tanto, heredaremos de
ella y crearemos una clase llamada

AJAXHttpRequestHandler, ver Listado 1


(todos los listados de este artculo pueden descargarse del articulo [4] del cuadro de Recursos.
Un servidor HTTP recibe distintos
tipos de comandos, pero el que nos interesa es el comando GET. El cual se usa
para solicitar informacin al servidor.
Cada vez que se realice una peticin GET
se invocar el mtodo do_GET de
BaseHTTPRequestHandler, as que lo
vamos a redefinir en nuestra clase.
Cuando se invoque do_GET, en la
variable de instancia self.path se encuentra la ruta solicitada por el cliente.
Nosotros contrastaremos esta ruta contra
las que aceptamos. Si no se encuentra
entre ellas, devolveremos la clebre pgi-

Listado 2: Fichero index.html


01 <html>
02 <head>
03 <title>Pruebas con AJAX</title>
04 <link rel=stylesheet
href=estilo.css
type=text/css />
05 <script language=Javascript
06 src=ajax.js>
07 </script>
08 </head>
09 <body>
10 <div id=documento>
11 <h3 id=titulo>Informacin
del sistema</h3>
12
<input type=button
name=button value=Procesos
13
onclick=javascript:haz
Peticion(ps.xml); />

14

<input type=button
name=button value=Disco
15
onclick=javascript:haz
Peticion(df.xml); />
16
<input type=button
name=button value=Usuarios
17
onclick=javascript:haz
Peticion(who.xml); />
18
<input type=button
name=button value=Mquina
19
onclick=javascript:haz
Peticion(uname.xml); />
20
<div id=contenedor>
21
<div id=datos></div>
22
</div>
23 </div>
24 </body>
25 </html>

PYTHON

35

INTEGRACIN

Ajax

<?xml version="1.0"?>
<salida>
<linea>linea de salida</linea>
...
<linea>linea de salida</linea>
...
<linea>linea de salida</linea>
</salida>
Figura 2: La clase BaseHTTPRequest genera
una entrada por comando.

na 404, indicando que la pgina solicitada no existe. La informacin se devuelve


usando el mtodo self.wfile.write(), lo
que en l escribamos ser devuelto al
cliente que realiz la peticin.
Adems del fichero index.html, ofreceremos una serie de servicios en forma de
ficheros XML. Estos servicios consistirn
en la ejecucin de un comando de sistema, y la conversin de su salida en un
fichero XML. El formato del fichero ser
muy sencillo:

Por lo que generaremos el fichero XML


a mano, sin hacer uso de libreras. De
esta manera, cuando el cliente solicite el
fichero ps.xml, nuestro servidor ejecutar el comando ps afx, crear el fichero
ps.xml con la salida del comando y se lo
enviar al cliente.

Definicin de Servicios
Para permitir que la definicin de servicios
sea lo ms simple posible, basta con introducir una nueva entrada en el diccionario
acciones de la clase AJAX HTTP
RequestHandler con la siguiente estructura:

<ruta> : [<mtodo_a_invocar>,U
<comando_a_ejecutar>],

Cuando se gestiona el comando HTTP


GET, se busca en este diccionario la ruta.
En caso de que est presente, se ejecutar el mtodo almacenado usando como
parmetros la ruta y el comando. Esto
nos da gran flexibilidad, aadir un nuevo
servicio consiste en introducir una nueva
linea de cdigo.
Existe un detalle importante, todo fichero
devuelto usando HTTP debe tener una
cabecera con una serie de lneas con formato llave: valor que dan informacin al cliente, el navegador, sobre el fichero devuelto.
Debido a problemas de espacio hemos decidido devolver slo el formato del fichero.
Para ello se invoca el mtodo envia_cabecera desde envia_respuesta antes de enviar el
fichero en s. Es de esta manera como el
navegador determina el tipo de fichero que
recibe y sus caractersticas.

Listado 3: Fichero ajax.js.


01 // GLOBALES
02 var http_request = false;
03
04 function hazPeticion(url) {
05 http_request = false;
06 http_request= new XMLHttpRequest();
07 if (http_request.overrideMimeType) {
08 http_request.overrideMimeType(text/xml);
09 }
10
11 if (!http_request) {
12 alert(Error al crear la instancia de XMLHttp
Request.);
13 return false;
14 }
15
16 // Esto es un callback, que se dispara al terminar de
17 // descargar el fichero xml.
18 http_request.onreadystatechange = modifica
Contenido;
19 http_request.open(GET, url, true);
20 http_request.send(null);
21 }
22 // Elimina todo elemento con id linea
23 function vaciaContenido(){
24 var d = document.getElementById(contenedor);
25 while(document.getElementById(linea0) ||
document.getElementById(linea1)){
26 nodo = document.getElementById(linea0);
27 if (! nodo){
28 nodo = document.getElementById(linea1);
29 }
30 var nodo_basura = d.removeChild(nodo);
31 }
32 }
33
34 // Carga el resultado del XML

36

PYTHON

35 function modificaContenido() {
36 if (http_request.readyState == 4) {
37 if (http_request.status == 200) {
38 vaciaContenido();
39
40 var xmldoc = http_request.responseXML;
41 var root =
xmldoc.getElementsByTagName(salida).item(0);
42
43 var fondo = 0;
44
45 for(var i = 0; i < root.childNodes.length; i++){
46
var nodo = root.childNodes.item(i);
47
48
var contenedor = document.getElementById
(contenedor);
49
var p = document.createElement(p);
50
// Truco para los colores ;)
51
52
if (fondo == 0) {
53
p.setAttribute(id,linea0);}
54
else {
55
p.setAttribute(id,linea1);
56
}
57
fondo = 1 - fondo;
58
var titulo = document.getElementById(datos);
59
60
p.textContent = nodo.firstChild.data;
61
contenedor.insertBefore(p,titulo);
62
63 }
64
65 } else {
66 alert(Hubo un problema con la peticin.);
67 }
68 }
69 }

W W W. L I N U X - M A G A Z I N E . E S

Figura 3: Nuestra pgina devolver los resultados de la consulta


sin recargar.

Cuando arranquemos el servidor, veremos que van apareciendo mensajes correspondientes a los distintos comandos
que se le mandan. La clase BaseHTTPRequest genera una
entrada por cada una. Ver figura 2.

Gestores de Servicios
Para gestionar los servicios se han creado las clases Pagina
y Comando, que responden a los mismos mtodos. La primera, Pagina, se encarga de las peticiones de ficheros de
texto, que en nuestro programa se reducen al fichero
index.html y sus ficheros supletorios, fichero Javascript y
CSS. Bsicamente, los carga en una variable y, mediante el
mtodo contenido, las dems clases tienen acceso a los mismos. En este caso, el parmetro ruta no afecta a esta clase,
pero se ha definido para que guarde compatibilidad con la
clase Comando.
La clase Comando ejecuta el comando especificado y permite el acceso al texto devuelto por l mismo mediante el
mismo mtodo que la clase Pagina. De esta manera son
intercambiables.
Ambas clases poseen un mtodo tipo() que devuelve el
tipo de fichero que almacenan. En el caso de Comando,
siempre ser un fichero XML, pero Pagina debe adivinar
el tipo de los ficheros que devuelve. Para ello se comprueba
la extensin de los mismos. En aras de la simplicidad, slo
consideraremos tres extensiones.
Cuando un comando es ejecutado por Comando, se tiene
especial cuidado en utilizar la funcin cgi.escape sobre cada
lnea. Esta funcin realiza algunas conversiones dentro del
texto que se le pasa para que pueda ser correctamente
visualizado por un navegador web. El problema radica en
que ciertos caracteres, como pueden ser < or son
especiales y si no se escapan, si no se preceden de un \,
causarn problemas.
Y con esto, hemos definido un servidor web bsico.
Python nos permite realizar complejas tareas con poco cdigo y este es uno de esos casos. Vayamos ahora a por AJAX
para comprenderlo.

HTML
Quiz ste sea uno de los artculos donde Python tenga
menor protagonismo, pero con cinco actores suele ser complicado. Veamos el HTML. La pgina index.html se puede
ver en el Listado 2.

W W W. L I N U X - M A G A Z I N E . E S

INTEGRACIN

Ajax

Bsicamente, carga un fichero


Javascript, un fichero CSS y muestra un
ttulo junto a unos cuantos botones.
Estos botones invocan acciones en
Javascript.
Debemos fijarnos especialmente en el
uso del atributo id en numerosas etiquetas HTML. Gracias a estos ids
podremos
manipularlas
mediante
Javascript.

Javascript, desde Otra Perspectiva


Mucha gente ha tenido extraos encuentros con este lenguaje de programacin.
Es raro y, hasta hace no demasiado, no
muy til. Te permita modificar colores
en pginas web o poner insidiosos banners. Por no hablar de las famosas ventanas emergentes.
Esto ha hecho que se haya ganado una
fama muy mala, tal es as que casi todos
tenemos restricciones en nuestro navegador en torno a qu acciones puede o no
realizar Javascript. Lo ms normal es
que tengamos uno de esos famosos bloqueadores de popups.
Pero Javascript se ha reinsertado en la
sociedad de los programadores por la
puerta grande gracias a un solo objeto.

Listado 4: Fichero estilo.css


01 #documento{
02 margin-left: 100px;
03 }
04
05
06 #titulo{
07 text-decoration: underline;
08 }
09
10
11
12 #linea0 {
13 margin: 0px;
14 padding-left: 20px;
15 background: #e0e0e0;
16 font-family: monospace;
17 }
18
19 #linea1 {
20 margin: 0px;
21 padding-left: 20px;
22 font-family: monospace;
23 }
24
25 #contenedor{
26 border-style: dashed;
27 border-width: 1px;
28 width: 600px;
29 border-color: black;
30 }

38

PYTHON

Nos referimos al ahora famoso


XMLHttpRequest.
Si vemos el cdigo del Listado 3, veremos un lenguaje que quiz nos recuerda
a Java. En realidad no tienen absolutamente nada que ver, aparte del nombre.
En nuestro ejemplo observamos tres funciones:
hazPeticion()
modificaContenido()
vaciaContenido()
Javascript, adems de muchas de las
caractersticas presentes en otros lenguajes (y algunas ausentes) dispone de una
coleccin de objetos que le permiten realizar operaciones. A da de hoy los ms
importantes son:
Manipulacin DOM
Manipulacin XML
XMLHTTP
DOM permite a Javascript manipular, en
tiempo real, el contenido de la pgina
web. Puede aadir, modificar o quitar
etiquetas y atributos, por lo que podemos operar sobre el documento de cualquier forma posible. Javascript puede
manipular un fichero XML de igual
forma que hace DOM.
Y la gran novedad, Javascript puede
realizar conexiones ASNCRONAS con el
servidor. Y resalto en mayscula la palabra ASNCRONAS porque ah est la
clave.
Esto significa que podemos hace conexiones, traernos documentos XML del
servidor y realizar operaciones DOM o
de cualquier otro tipo, cuando queramos!
El usuario carga su pgina web, y
una vez cargada, sin necesidad de
recargarla, podemos modificarla a
nuestro antojo en base a informacin
que podemos pedir al servidor en cualquier momento.
Volviendo a nuestras funciones, la funcin hazPeticion() recibe una url, crea el
objeto XMLHttpRequest y despus de
algunas comprobaciones, asigna una
funcin para que sea invocada cuando el
fichero que esa url especifica sea completamente descargado.
Esto significa que mientras leemos
nuestra web, Javascript estar bajando
un fichero y, cuando finalice, llamar a
la funcin modificaContenido.
Y qu hace esta funcin? Comprueba
el estado de la peticin, (el estado 200 el
de todo correcto y el 404 el de lo sentimos mucho, pero el fichero solicitado

no est disponible) y entonces obtiene


el documento XML del objeto que gestionaba la conexin.
Pero antes invoca a vaciaContenido(),
que localiza toda etiqueta con las ids
linea0 o linea1 y las elimina de la
pgina. Hacemos esto porque a continuacin las volvemos a introducir en la
pgina, pero esta vez con los datos frescos del servidor.
No queremos entrar en los detalles,
ya que, en teora, esto es un artculo
sobre Python, no Javascript, pero bsicamente esto es lo que hace el fichero
ajax.js.
El fichero estilo.c, que aparece en el
listado 4, simplemente configura los
colores y caractersticas de algunas de
las etiquetas para que el aspecto mejore.
Y se acab, aqu tenemos nuestra aplicacin AJAX.
El resultado final ser el que vemos en
la figura 3. Cuando pulsemos cualquiera
de los botones, se cargar la salida de
texto de la ejecucin asociada a cada uno
de ellos en pantalla, pero sin recargar la
pgina. Si queremos aadir un nuevo
comando, slo tenemos que introducir la
lnea correspondiente en acciones en server.py y aadir un nuevo botn como los
que ya existen en index.html.

Conclusin
Es tan complicado eso de AJAX? Por
supuesto que no! Lo que ocurre es que
es una palabra que se est convirtiendo
en un mito, pero no deja de ser una astuta combinacin de programacin en el
servidor y cliente adems del uso intensivo de XMLHttpRequest.
Poco a poco AJAX est poblando todas
las pginas webs y la mayora de los
currculos vitae. Quin sabe, lo mismo
dentro de dos aos esa palabra tenga
tanto poder como otra palabra de cuatro
letras: J2EE.

Recursos
[1] Jesse James Garret define AJAX:
http://adaptivepath.com/ideas/
ajax-new-approach-web-applications
[2] Explicacin de AJAX en Wikipedia:
http://en.wikipedia.org/wiki/AJAX
[3] Sarissa: http://sarissa.sourceforge.net/
doc/
[4] Listados de este artculo: http://www.
linux-magazine.es/Magazine/
Downloads/Especiales/06_Python

W W W. L I N U X - M A G A Z I N E . E S

.Net

INTEGRACIN

.Net y Python con Ironpython

De Serpientes
y Primates
.NET est avanzando, y Python no se ha quedado atrs. En lugar de combatirla, ha entrado
en simbiosis con ella. Con Ironpython podremos hacer uso de toda la potencia de .NET desde
nuestro lenguaje favorito. Por Jos Mara Ruz.

e l
Isse
Eric
F
123R

En el ao 2003,
Jim Hugunin ley
que la plataforma
de Microsoft .NET
no era adecuada para
la creacin de lenguajes
dinmicos (un lenguaje
dinmico es aquel al que
no debes decirle qu tipo
de variable vas a usar)
Hugunin se extra
bastante
del
comentario porque
ya haba creado un
intrprete
de
Python, Jython,
para la mquina
virtual de Java. Este intrprete consigui
cierto reconocimiento, y prueba de ello
es el artculo sobre Jython que aparece
en el nmero 7 de esta misma revista.

Python sobre .NET


As que ni corto ni perezoso Jim se tom el
comentario como algo personal y se puso
manos a la obra. Como resultado de su
esfuerzo, el 5 de Septiembre de 2006 apareci la versin 1.0 de IronPython [1), nombre
que dio a su intrprete [2].
Antes de la aparicin de la versin 1.0,
IronPython ya haba llamado la atencin de
gran cantidad de desarrolladores. La posibilidad de crear aplicaciones grficas o web
multiplataforma aprovechando los recursos
de .NET era demasiado interesante para
dejarla escapar.
Incluso Miguel de Icaza, de fama mundial
gracias a Gnome y Mono, reconoci la
importancia de IronPython para Mono en el
artculo que aparece referenciado en el
Recurso [3]: Prcticamente cada nueva versin de IronPython ha expuesto las limitaciones de nuestro runtime (Mono), nuestras
libreras de clases o nuestros compiladores.

W W W. L I N U X - M A G A Z I N E . E S

IronPython realmente ha contribuido para


que Mono se convierta en un mejor runtime.
Vamos a echar un buen vistazo a las posibilidades de IronPython y veremos cmo
nos permite explotar el poder de las libreras
.NET, pero siempre desde nuestro lenguaje
favorito.

Preparativos

Figura 1: Hola Mundo con WinForms.

Para poder hacer uso de IronPython necesitamos instalarlo. IronPython es una impletodo aquel que haya trabajado mucho en la
mentacin de Python sobre .NET, por lo que
consola de Linux, porque Control z sirve
necesitamos una implementacin de .NET
para mandar a un programa a background.
para Linux.
La secuencia sera:
Mono es la implementacin libre de .NET
ms famosa. Como vimos antes, Miguel de
01 IronPython 1.0 (1.0) on
Icaza est usando IronPython como banco
.NET 2.0.50727.42
de pruebas para Mono. Esto nos asegura
02 Copyright (c) Microsoft
que IronPython funcionar bastante bien
Corporation. All rights
sobre Mono. Tenemos que instalar IronPyreserved.
thon y Mono.
03 >>> quit
Falta un componente en la ecuacin: libg04 Use Ctrl-Z plus Return to exit
diplus (ver Recurso [4]). En este artculo
05 >>> exit
haremos uso de la interfaz grfica de usua06 Use Ctrl-Z plus Return to exit
rio que .NET pone a disposicin de sus des07 >>>
arrolladores. Este sistema, llamado Win08 Suspended
Forms, ha sido reimplementado en forma de
09 >
software libre en la librera libgdiplus. Sin
10 >
ella no podremos hacer uso de WinForms, y
por ello necesitamos instalarla.
Jim tuvo que crear este sistema para que
Un detalle tambin algo molesto, cmo
funcionara tanto en Windows como en
se sale del intrprete?
La verdad es que es
Listado 1: Mostrar el Contenido de un Fichero de Texto
algo rebuscado. No
01 import clr
funcionan ni exit ni
02
quit. Cuando ejecute03 from System.IO import *
mos cualquiera de los
04
05 fichero = File.OpenText(mifichero.txt)
dos, seremos infor06
mados de que debe07 linea = fichero.ReadLine()
mos pulsar Control z
08
para salir del intr09 while s:
prete. Esto puede
10 print linea
resultar curioso para

PYTHON

39

INTEGRACIN

.Net

Copyright (c) MicrosoftU


Corporation. All rightsU
reserved.
>>>

Figura 2: Visor de texto.

Linux. Si ya tienes todos estos paquetes instalados en tu distribucin, podemos pasar al


siguiente paso el infame Hola Mundo.

Hola Mundo Consola


La primera sorpresa de IronPython es que
realmente es casi igual a Python pero ms
lento. Para ejecutar un programa .NET en
Linux debemos emplear el comando mono
que lo ejecuta dentro de la mquina virtual
de .NET.
> mono miprograma.exe

Probablemente, el comando ironpython de


vuestra distribucin sea en realidad un script
shell que ejecuta el intrprete (llamado
ipy.exe) empleando mono. El resultado ser:
IronPython 1.0 (1.0) onU
.NET 2.0.50727.42

Microsoft Corporation? Tranquilos, tranquilos! IronPython posee una licencia casi


libre, parte del Microsofts Shared Source.
Aunque su licencia no est aprobada por
OSI, Jim dice que la licencia que lo cubre
sigue todas las normas de OSI (ver Recurso
[5]).
Vayamos al grano, nuestro hola mundo
es reconocible:
>>> print Hola Mundo
Hola Mundo
>>>

Como el lector podr apreciar, no ha cambiado nada. IronPython mantiene una gran
compatibilidad con Python. Pero no todo el
monte es organo. Debido a que se ejecuta
sobre .NET, IronPython no puede acceder a
libreras de Python escritas en C. Esto es un
problema porque, por ejemplo, la librera
sys est escrita en C.
Otro detalle bastante molesto es que el
intrprete de IronPython no tiene historia, ni
autocompletacin, ni nada de nada. Esto
puede poner a ms de uno de los nervios,
yo al menos me puse, por lo que es recomendable trabajar en un fichero y ejecutarlo
con IronPython.

Listado 2: Hola Mundo con WinForms


01 import clr
02 clr.AddReference(System.
Drawing)
03 clr.AddReference(System.
Windows.Forms)
04
05 from System.Drawing import
Color, Point
06 from System.Windows.Forms import
(Application, BorderStyle, Button, Form, FormBorderStyle,
Label, Panel, Screen)
07
08 class HolaMundo(Form):
09 def __init__ (self):
10 self.Text = Hola Linux
Magazine
11 self.FormBorderStyle =
FormBorderStyle.FixedDialog
12
13 pantalla = Screen.GetWorkingArea(self)
14 self.Height = pantalla.Height /
5
15 self.Width = pantalla.Width / 5
16

40

PYTHON

17 self.panel1 = Panel()
18 self.panel1.Location = Point
(0,0)
19 self.panel1.Width = self.Width
20 self.panel1.Height =
self.Height
21
22 self.generaSaludo()
23
24
self.panel1.Controls.Add(self.l
abel1)
25 self.Controls.Add(self.panel1)
26
27 def generaSaludo(self):
28 self.label1 = Label()
29 self.label1.Text = Hola
lectores de Linux Magazine
30 self.label1.Location =
Point(20,20)
31 self.label1.Height = 25
32 self.label1.Width = self.Width
33
34 form = HolaMundo()
35 Application.Run(form)

Libreras .NET
Hasta el momento no hemos hecho nada
especial, es hora de comenzar con lo interesante. IronPython est escrito en C# sobre
.NET. Una de las virtudes de .NET es que
una vez que ha sido compilado a su lenguaje intermedio, puedes hacer uso de cualquier librera escrita en cualquier lenguaje
para .NET.
IronPython tiene a su disposicin todas
las libreras del proyecto Mono. Esto incluye
la librera para programacin de aplicaciones grficas WinForms, las libreras para
programacin web ASP.NET, estructuras de
datos, libreras de cifrado, a las que hay
que sumar aqullas que ha incorporado el
proyecto Mono, como por ejemplo las que
nos permiten empotrar el motor Gecko (de
Firefox) en una aplicacin grfica, de forma
que podemos crear un navegador web con
muy poco cdigo.
Veamos un ejemplo de uso. Vamos a abrir
un fichero y a mostrarlo e imprimirlo en el
terminal, ver Listado 1. No es muy complicado verdad? Lo ms importante de este
ejemplo es la sentencia:
import clr

que nos permite hacer uso de todas las libreras de .NET.

Hola Mundo Winforms


Hasta ahora todo lo que hemos hecho era
fcilmente realizable con Python, a partir
de este momento observaremos cambios.
Vamos a realizar el mismo Hola Mundo
pero empleando WinForms. Microsoft ha
simplificado bastante el desarrollo de aplicaciones grficas con esta librera. IronPython lo ha simplificado an ms.
WinForms, ver Recurso [6], funciona de
forma similar a como lo hacen otras libreras grficas. Creas una ventana, dentro de
la cual creas un panel, dentro del cual se
pueden disponer widgets. Es muy parecido
a las muecas rusas Matroskas. Veamos el
cdigo en el Listado 2 y el resultado en la
Figura 1.
Comencemos por el principio. Una vez
importado clr, hemos de hacer algo que no
es normal en Python: debemos aadir referencias a las libreras de .NET que vamos a
utilizar. Para ello utilizamos el mtodo
clr.AddReference() con el nombre de la librera que vamos a usar.
Winforms hace uso de System.Drawing
y de System.Windows.Forms. Estas libreras contienen todos los widgets necesarios

W W W. L I N U X - M A G A Z I N E . E S

.Net

para crear aplicaciones grficas. Una vez


que aadamos las referencias a estas libreras, podemos usarlas como cualquier librera de Python.
Importamos todos los widgets necesarios: Application, BorderStyle, Button
Para hacer referencias a posiciones en la
pantalla emplearemos la clase Point de
System.Drawing, expresando la posicin en
pixels.
Con todas las libreras cargadas, podemos comenzar inicializando nuestra clase
HolaMundo, que representa la ventana de
la aplicacin. Comenzamos dndole
ttulo, con self.Text, a la ventana. Definimos el tipo de ventana que utilizaremos
con FormBorderStyle indicando que ser
fijo, nuestra ventana no se podr redimensionar.

Calculamos el tamao de la ventana en


base al de la pantalla. Conseguimos los
datos de la pantalla mediante el mtodo
Screen.GetWorkingArea(), y hacemos que
nuestra ventana tenga un quinto de la
altura (Height) y ancho (Width) de la
pantalla. Podramos haber indicado el
tamao mediante un nmero, digamos
100 pixels.
Creamos un panel que pasar a contener
todos los widgets que utilicemos. De nuevo
ajustamos su altura y anchura, as como su
posicin dentro de la ventana. Como queremos que ocupe toda la superficie de la ventana lo posicionamos en (0,0), y le damos el
mismo ancho y la misma altura que la ventana. He aadido un mtodo, para simplificar el cdigo, que genera una etiqueta
donde realizamos el saludo. De nuevo el

INTEGRACIN

proceso es repetitivo: texto de etiqueta, posicin, altura y anchura.


Finalmente, aadimos la etiqueta al panel
y el panel a la ventana (esto ltimo
mediante self.Controls.Add()). Con estas
ltimas sentencias terminamos de definir
nuestra clase.
Para poder hacer uso de ella creamos una
instancia de HolaMundo y se la pasamos a
Application.Run(), que es un bucle sin fin
que se dedicar a gestionar los eventos
sobre la ventana.
La explicacin es bastante ms larga
que el texto, pero el lector se habr dado
cuenta de lo simple que es realmente el
proceso. Incluso llega a ser aburrido por
repetitivo.
Pero hemos logrado nuestro objetivo, realizar una aplicacin grfica con un mnimo

Listado 3: Visor de Texto Simple


01 import clr
02 clr.AddReference(System.
Drawing)
03 clr.AddReference(System.
Windows.Forms)
04
05 from System.IO import *
06 from System.Drawing import Color,
Point
07 from System.Windows.Forms import
(Application, BorderStyle,
Button, Form, FormBorderStyle,
Label, Panel, Screen, OpenFile
Dialog, DialogResult, TextBox,
ScrollBars)
08
09 class LectorTXT(Form):
10 def __init__ (self):
11 self.Text = Visor de texto Linux
Magazine
12 self.FormBorderStyle =
FormBorderStyle.FixedDialog
13
14 pantalla = Screen.
GetWorkingArea(self)
15 self.Height = 300
16 self.Width = 400
17
18 self.panel1 = Panel()
19 self.panel1.Location = Point
(0,0)
20 self.panel1.Width = self.Width
21 self.panel1.Height =
self.Height
22 self.panel1.BorderStyle =
BorderStyle.FixedSingle
23
24 self.generaLabel1()
25 self.generaLabel2()
26 self.generaBoton1()
27 self.generaAreaTexto()
28
29 self.panel1.Controls.Add

W W W. L I N U X - M A G A Z I N E . E S

(self.label1)
30 self.panel1.Controls.Add
(self.label2)
31 self.panel1.Controls.Add
(self.boton1)
32 self.panel1.Controls.Add
(self.areaTexto)
33
34 self.Controls.Add(
self.panel1)
35
36 def generaAreaTexto(self):
37 texto = TextBox()
38 texto.Height = self.Height / 2
39 texto.Width = self.Width - 30 #
para que no se salga
40 texto.Location = Point(20,110)
41 texto.Multiline = True
42 texto.ScrollBars =
ScrollBars.Vertical
43 self.areaTexto = texto
44
45 def generaLabel1(self):
46 self.label1 = Label()
47 self.label1.Text = Lector de
ficheros de texto Linux Magazine
48 self.label1.Location =
Point(20,20)
49 self.label1.Height = 25
50 self.label1.Width = self.Width
51
52 def generaLabel2(self):
53 self.label2 = Label()
54 self.label2.Text = Fichero
seleccionado: ??
55 self.label2.Location =
Point(20,50)
56 self.label2.Height = 25
57 self.label2.Width = self.Width
58
59 def generaBoton1(self):
60 self.boton1 = Button ()

61 self.boton1.Name= Botn 1
62 self.boton1.Text = Abrir
fichero
63 self.boton1.Location =
Point(20,80)
64 self.boton1.Height = 25
65 self.boton1.Width = 100
66 self.boton1.Click +=
self.abreFichero
67
68 def abreFichero(self, sender,
event):
69 color = OpenFileDialog()
70 color.Filter = Ficheros txt
(*.txt)|*.txt
71 color.Title = Selecciona un
fichero de texto
72
73 nombre =
74
75 if (color.ShowDialog() ==
DialogResult.OK ):
76 nombre = color.FileName
77 self.label2.Text = Fichero
seleccionado: + nombre
78 # cargamos el texto
79 fichero = File.OpenText
(nombre)
80 texto =
81
82 s = fichero.ReadLine()
83 while s :
84 texto += s
85 s = fichero.ReadLine()
86
87 self.areaTexto.Text = texto
88
89 form = LectorTXT()
90
91 Application.Run(form)

PYTHON

41

INTEGRACIN

.Net

Figura 3: Dilogo de eleccin de fichero.

de lneas de cdigo. Vayamos a algo ms


interesante.

Un Visor de Textos
Vamos a crear un visor de ficheros de texto
muy simple cuyo objetivo es explicar cmo
funciona la gestin de eventos en WinForms. Este es quiz el punto dbil de
muchas libreras grficas, pero en el caso de
WinForms se ha simplificado mucho.
Las aplicaciones grficas estn vivas. No
son programas insensibles que realizan operaciones, generan una salida y mueren. El
trmino exacto es interactivas. Para poder
interactuar con el usuario deben ser capaces
de responder a acciones que el usuario realiza.
A estas acciones se les denomina eventos.
Cuando creas una aplicacin grfica debes
responder ante estos eventos con acciones,
por evento existe una accin. Esta es la teora bsica.
En lo que difieren las diferentes libreras
grficas es en cmo se implementa la respuesta a los eventos. Los desarrolladores de
WinForms estaban hartos de tener que
escribir complicadas sentencias slo para
hacer que cuando pulses un botn cambie
de color. As que crearon el siguiente
diseo.
Cada widget de la aplicacin posee una
serie de eventos ante los cuales responde.
Estos eventos vienen representados como
variables dentro del widget que guardan listas de acciones a realizar cuando tal evento
suceda. As de simple. Veamos el cdigo del
Listado 3. En el mtodo generaBoton1 aparece la sentencia de asignacin de cdigo a
un evento:
self.boton1.Click +=U
self.abreFichero

42

PYTHON

Hemos definido un botn,


y queremos que cuando
se pulse se ejecute el
mtodo abreFichero. El
evento que se dispara
cuando pulsamos un
botn es Click, as que
usamos el operador +=,
que podramos llamar
suma y sigue, para aadirlo a lista de funciones o
mtodos a llamar cuando
aparezca ese evento. Es
realmente sencillo.
Pero, qu hacemos
cuando pulsamos el botn
en el Listado 3? El objetivo
de este programa es crear un editor de textos
sencillo, ver Figura 2. Para ello, en la inicializacin de la clase LectorTXT hemos puesto
en la ventana un par de etiquetas, una para
saludar, otra para decir qu fichero estamos
abriendo, un botn y un rea de texto.
Cuando pulsamos en el botn aparecer
el cuadro de dilogo de la Figura 3. Para crearlo slo hemos tenido que crear una instancia de OpenFileDialog dentro de abreFichero(). Este objeto define un cuadro de dilogo, pero no lo muestra, antes debemos
configurarlo. Slo queremos mostrar ficheros de texto, por lo que creamos un filtro que
nicamente permite ver ficheros de texto.
Para ello debemos almacenar en color.Filter
una cadena con un formato especial:
* Texto a mostrar | filtro

El filtro debe ser como los que empleamos


cuando usamos el terminal de Linux. El
smbolo * significa cualquier cadena de
texto, por lo que nuestro filtro solo permitir
ver aquellos ficheros compuestos por cualquier cadena de texto y la terminacin .txt,
que generalmente se emplea con los ficheros te texto. El cuadro dilogo tambin tiene
un ttulo, puesto que aparece en una ventana independiente.
Cuando todo est configurado invocamos
color.ShowDialog(), que bloquea nuestra
aplicacin hasta que el usuario ha escogido
un fichero. Esta llamada devuelve un resultado, que debemos comprobar. En nuestro
caso, slo seguimos si el resultado ha sido
DialogResult.OK. Si el usuario cierra el cuadro de dilogo, no se har nada por ejemplo.
Si todo ha ido bien, entonces cogeremos
de color.FileName el nombre del fichero
seleccionado y emplearemos un cdigo muy

parecido al que vimos en el Listado 1 para


cargar el texto en una variable. Cuando lo
tengamos, cargaremos el texto en la variable
self.areaTexto.Text, lo que provocar que se
modifique el contenido del rea de texto.

Conclusin
Poder acceder a la enorme librera de .NET
con IronPython nos permite crear aplicaciones grficas multiplataforma. Una posibilidad realmente esperanzadora para todos
aquellos que quieran llevar sus desarrollos
en Python de un equipo a otro.
El lector puede profundizar en el
desarrollo de aplicaciones que empleen
WinForms desde IronPython en el Recurso
[7]. Es un tutorial, en ingls, en el que se da
un repaso a los conocimientos bsicos de
desarrollo de aplicaciones grficas mediante
WinForms.
Python est consiguiendo con IronPython
atraer a gran nmero de desarrolladores de
otros sistemas operativos, de forma que se
estn comenzando a crear aplicaciones de
las que nosotros podremos disfrutar en
Linux gracias a Mono.
.NET se va estableciendo poco a poco en
el mundo empresarial como un estndar a
tener en cuenta. Pero esto no debe atemorizar a los que usen Python. Tanto si triunfa
.NET o Java, IronPython y Jython estn ah
para que Python siga vigente y demostrando al mundo que la programacin no

tiene por qu ser complicada.

Recursos
[1] Sitio de IronPython:
http://www.
codeplex.com/IronPython
[2] Jim Hugunin anuncia IronPython:
http://blogs.msdn.com/hugunin/
archive/2006/09/05/741605.aspx
[3] Miguel de Icaza anuncia la implementacin libre de C#: http://tirania.org/blog/archive/2007/Jan-111.html
[4] API GDI+ para plataformas no Windows:
http://www.mono-project.com/Libgdiplus
[5] La licencia Apache de IronPython:
http://www.codeplex.com/license?Pr
ojectName=IronPython
[6] WinForms: http://www.mono-project.com/WinForms
[7] Tutoriales WinForms: http://www.
voidspace.org.uk/ironpython/
winforms/index.shtml
[8] Listados de este artculo:
http://www.linux-magazine.es/
Magazine/Downloads/
Especiales/06_Python

W W W. L I N U X - M A G A Z I N E . E S

Qt

INTEGRACIN

QT4 trae a Python nuevas mejoras en la creacin de programas

Nedelcu Sorin, www.sxc.hu

Desarrollo
Rpido!

Ha llegado el cliente y te lo ha dejado claro: necesita el programa para ayer. Ha surgido un problema enorme
y es necesario resolverlo en tiempo rcord. La desesperacin se palpa en el ambiente y todos los ojos miran a
tu persona. Devuelves una mirada de confianza y dices con tono tranquilo: No te preocupes, tengo un arma
secreta para acabar con el problema. Por Jos Mara Ruz

Algunos deciden dejar la habitacin,


otros prefieren quedarse a tus espaldas,
nerviosos, con los brazos cruzados y la
mirada fija en ti. El problema est claro:
viene en camino un gran envo, probablemente llegue hoy, con una gran cantidad de productos que se deben controlar
porque en unos das saldrn con destino
a un cliente. La ltima vez se hizo todo
el proceso con hojas de clculo, pero fue
un desastre. Esta vez quieren estar preparados.
Es necesario desarrollar una aplicacin y que funcione en menos de 2
horas.
Qt4 es la mejor opcin.

Qt4
Qt4 es la librera sobre la que se basa el
escritorio KDE4. Con licencia GPL, es
una alternativa perfecta para desarrollar
software libre. Posee algunas ventajas
envidiables:
Es muy completa (incorpora cdigo de
acceso a base de datos, tratamiento
svg, generacin de PDF)
Es integrada (slo necesitas QT, no
cientos de minipaquetes)
PyQT es un proyecto maduro

W W W. L I N U X - M A G A Z I N E . E S

Posee un entorno de desarrollo rpido


de interfaces profesional: designer
Estas caractersticas hacen de Qt4 una
librera perfecta para el desarrollo rpido
de programas. Adems es una librera
totalmente multiplataforma, podemos
desarrollar una sola vez y ejecutar donde
queramos.

cual debemos crear en primer lugar una


ventana normal y corriente (llamada en
ingls MainWindow), como la que
aparece en la Figura 2, y con la que trabajaremos durante todo el artculo. Por
defecto viene con un barra de estado y
otra de men, con la que comenzaremos. Para ello hay que pulsar dos veces

Designer
Una de las ventajas de
Qt4 (ver Recurso [1])
es su designer. Es una
herramienta, curtida en
desarrollos profesionales, que nos permite
comenzar diseando el
aspecto grfico de la
aplicacin. Para ello
slo tenemos que ejecutar
el
comando
designer-qt4, y aparecer un programa compuesto por distintas
ventanas como las que
aparecen en la Figura
1.
Designer sirve para Figura 1: Designer permite crear interfaces de aplicaciones rpidacrear ventanas, para lo mente.

PYTHON

43

INTEGRACIN

Qt

Figura 2: Nuestro MainWindow vaca y sin


widgets.

con el ratn sobre Type Here y escribir


&Archivo. De esta forma habremos
creado nuestro primer men. Dentro de
este men introduciremos, de igual
forma, la accin &Salir. El smbolo &
delante de las palabras indica que quere-

mos que Qt4 genere teclas rpidas para


ellas. Por ejemplo, al poner un &
delante de Archivo hacemos que su
primera A sirva como tecla rpida,
activando el men cuando pulsemos
alt-a. El resultado sera el que vemos en
la Figura 3.
Vamos a trabajar con datos, as que lo
mejor que podemos hacer es emplear el
widget Table View, que nos permite trabajar con datos tabulares y que podremos conectar, ms adelante, con nuestra
base de datos de forma casi directa. Para
ello tenemos que ir a la ventana principal
de Designer y buscar en Item Views el
widget Table View. Debemos arrastrarlo
hasta la ventana, quedando algo pare-

cido a lo que podemos ver en la Figura 4.


Queda mal, no? Lo ideal sera que ocupase todo el espacio visible, para lo cual
tenemos que asignar un Layout (disposicin) al espacio en el que pondremos
nuestro Table View. Hay que pulsar con
el botn derecho sobre el espacio gris
que rodea al Table View, ver Figura 5, y
pulsar en la ltima opcin del men: Lay
out. En ella escogeremos Lay out Vertically, y nuestro Table View ocupar todo
el espacio.
Pulsamos dos veces con el ratn sobre
el Table View y aparecer una ventana
que nos permitir cambiar su nombre.
Esto es muy importante, ya que posteriormente nos referiremos a Table View

Listado 1: Nuestro Programa


01
02
03
04
05
06
07
08
09

#!/usr/local/bin/python
# -*- coding: utf-8 -*import sys
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4 import QtSql
from gui import Ui_MainWindow

class Programa(QtGui.
QMainWindow):
10 def __init__(self,
parent=None):
11
QtGui.QWidget.__init__(self,
parent)
12
13
self.modelo = self.
generaModelo()
14
15
self.ui = Ui_MainWindow()
16
self.ui.setupUi(self)
17
self.ui.tabla.setModel
(self.modelo)
18
19
20

self.reajusta()

QtCore.QObject.connect(
self.ui.action_Salir,QtCore.
SIGNAL(activated()),QtGui.qAp
p, QtCore.SLOT(quit()) )
21
QtCore.QObject.connect(
self.ui.refrescar,QtCore.
SIGNAL(clicked()),self.refres
car )
22
QtCore.QObject.connect(
self.ui.nuevaLinea,QtCore.
SIGNAL(clicked()),self.nuevaLinea )
23

44

QtCore.QObject.connect(
self.ui.eliminarLinea,QtCore.
SIGNAL(clicked()),

PYTHON

self.eliminarLinea )
24
25

51
52

def generaModelo(self):

def eliminarLinea(self):
index = self.ui.tabla.
currentIndex()

26

self.conectaDB()

53

27

modelo = QtSql.

54

QSqlTableModel(None, self.db)

fila = index.row()
ean13 =
self.modelo.data(self.modelo.in

28

modelo.setTable(inventario)

dex(fila, self.recordPro-

29

modelo.setSort( self.

totipo.indexOf(ean13))).
toString()

recordPrototipo.indexOf(ean13
), QtCore.Qt.AscendingOrder)

55

nombre =

30

modelo.select()

self.modelo.data(self.modelo.

31

return modelo

index(fila, self.record
Prototipo.indexOf(nombre))).

32
33
34

self.db = QtSql.QSql
Database.addDatabase(QPSQL)

35
36

toString()

def conectaDB(self):
56
57

self.db.setHostName(rufus)
self.db.setDatabaseName

58

(inventario)
37

if QtGui.QMessageBox.
question( self, Borrar linea,
QtCore.QString(
Desea borrar el producto #%1,

self.db.setUserName

%2? ).arg(ean13).arg

(josemaria)

(nombre),

38

self.db.setPassword()

59

39

name = self.db.open()

QtGui.QMessageBox.Yes|

40

query = QtSql.QSqlQuery(

QtGui.QMessageBox.No) ==

select * from
inventario,self.db)
41

self.recordPrototipo =
query.record()

42
43
44

60

self.modelo.removeRow(fila)

61

self.reajusta()

62
63

def reajusta(self):
self.ui.tabla.resize
ColumnsToContents()

45
46

QtGui.QMessageBox.Yes:

64

def refrescar(self):
self.modelo.select()

65
66 if __name__ == __main__:
67

def nuevaLinea(self):

app = QtGui.QApplication(
sys.argv)

47

fila = self.modelo.rowCount()

68

myapp = Programa()

48

self.modelo.insertRow(fila)

69

myapp.show()

49

self.reajusta()

70

sys.exit(app.exec_())

50

W W W. L I N U X - M A G A Z I N E . E S

Qt

Figura 3: Nuestro men, esperando las


opciones.

por este nombre, por lo que lo bautizaremos con uno que sea claro y conciso
(aunque no muy imaginativo): tabla.
Nuestra tabla, tal como aparece aqu,
nos permitira ver datos, incluso editarlos, pero no aadir ni tampoco borrar.
Para ello necesitamos botones que inicien acciones externas. Emplearemos
Tool Buttons que nos permiten utilizar
imgenes en su interior. Estos botones
los solemos ver en las aplicaciones justamente debajo del men. Dispondremos 3
de estos botones: uno para refrescar los
datos, uno para aadir y otro para borrar.
El botn de refresco es muy importante,
puesto que al trabajar varias personas a
la vez en la base de datos puede darse el
caso de que necesitemos saber si se ha
introducido ya algn dato en particular.
Estos 3 botones los situaremos entre el
men y la tabla, quedando como puede
apreciarse en la Figura 6. Pero, por qu
aparecen as? No sera mejor que apareciesen a lo ancho? S, lo sera, pero hemos
indicado a designer que disponga los widgets verticalmente. La solucin consiste
en indicarle ahora que los disponga horizontalmente, para lo cual slo tenemos
que seleccionarlos con el ratn (como si
fueran ficheros, enmarcndolos en un
cuadrado de seleccin), pulsar el botn
derecho sobre ellos y en Lay outs seleccionar Break LayOut. Con esta accin
deshacemos la disposicin que elegimos
anteriormente, pero para rehacerla, volvemos a pulsar el botn derecho, y para
estos tres botones seleccionados elegimos
Lay out Horizontally, dejndolos como
aparecen en la Figura 7. Volvemos a pulsar con el botn derecho, aunque ahora
sobre el rea gris entre los botones y el
Table View, y seleccionamos Lay Out Vertically, quedando todos los widgets como
en la Figura 8, con lo que prcticamente
hemos acabado con el diseo de la interfaz. Nos quedan unos retoques.

W W W. L I N U X - M A G A Z I N E . E S

INTEGRACIN

A los botones hay que darles nombre. Para hacerlo


slo tenemos que pulsar el
botn derecho sobre ellos y
elegir Change objectName.
Los llamaremos, respectivamente: refrescar, nuevaLinea, eliminarLinea. El
ltimo retoque consiste en
asignarles unos iconos, de
forma que sean identificables visualmente. Para esto
lo mejor es darse un paseo
por los directorios de iconos
de Gnome o Kde y seleccio- Figura 4: Table View descolocada hacia el lado izquierdo.
nar los ms acordes. Los
copiamos a nuestro directorio de
face), que llamaremos gui.ui. Este
desarrollo, y en designer pulsamos la
fichero describe la interfaz grfica que
combinacin de teclas control-i. Aparehemos diseado, aunque no lo podemos
cer el Property Editor que nos permite
emplear directamente, sino que hay que
cambiar los parmetros de los widgets.
compilarlo. Para ello utilizamos el proEstamos interesados en los de los botograma pyuic4:
nes, por lo que slo tenemos que selecjosemaria@rufus> pyuic4U
cionar un botn para que el Property Edigui.ui > gui.py
tor cambie para mostrar los parmetros
de ese botn. Vamos a cambiar el parEl resultado de compilar este fichero es la
metro Icon, que est en la seccin QAbsgeneracin de un fichero Python que reatractButton. Si pulsamos en l veremos
liza todo lo necesario para que tengamos
que a la derecha hay un botn que nos
nuestra interfaz grfica funcionando.
permite seleccionar entre Choose
Ahora necesitamos crear un programa
Resource y Choose File.... La primera
que, usando este fichero como librera,
opcin sirve para guardar en un solo
gestione la aplicacin que estamos crefichero, llamado de recursos, todos los
ando.
ficheros que vaya a necesitar nuestra
Miramos nuestro reloj, slo han
aplicacin, lo que simplifica la instalapasado 15 minutos, nuestros compaeros
cin. La segunda opcin, ms simple,
nos
permite
indicar el nombre del fichero
que emplearemos. Utilizaremos la segunda
opcin, Choose
File..., por ser
ms
simple.
Seleccionamos
un fichero grfico
con
el
icono, y hacemos lo mismo
para los otros
botones,
ver
Figura 9.
Cuando guardemos nuestro
diseo generaremos un fichero
con
extensin Figura 5: El men Lay out nos permite colocar correctamente los ele.ui (User Inter- mentos.

PYTHON

45

INTEGRACIN

Qt

miran por encima de nuestro hombro


tranquilos. Pero sabemos que esta interfaz necesita de un cerebro para controlarla, por el momento es slo una
fachada.

La Base de Datos
Usaremos una base de datos remota Postgresql para almacenar los datos, de esta
forma varias personas podrn trabajar a
la vez en el programa. En lugar de
emplear las libreras que existen para trabajar con Postgresql en Python usaremos
Qt4. La tabla que crearemos ser tambin
sencilla:
create table inventario (
ean13 char(13) primary key,
nombre varchar not null,
cantidad int not nullU
default 0,
constraintU
inventario_cantidad_positivoU
check(cantidad >= 0)
)

Qt4 trae consigo una librera de control


de base de datos muy avanzada: QtSql.
No se restringe a mandar cdigo sql, adems interacta directamente con los widgets de Qt usando un patrn de diseo
denominado MVC (modelo vista controlador).

Figura 6: Creamos tres nuevos botones

Figura 7: Y los alineamos a nuestro gusto.

46

PYTHON

Esta tcnica consiste en


separar el cdigo de manipulacin de los datos, el cdigo
que los muestra y el que
toma las decisiones. Qt provee dos clases diferentes, una
que representa los datos (el
modelo) y otra que representa la visualizacin de los
mismos (el visualizador),
dejando el control de la apli- Figura 8: sta ser la disposicin final de los botones y
cacin en nuestras manos. El tabla.
modelo es una entidad autnoma e independiente, no requiere de la
La primera de ellas realiza un select sobre
existencia de una base de datos. Podela tabla inventario, lo que no es muy
mos crear un modelo con tablas y filas,
extrao, pero la segunda lnea emplea el
con relaciones y llaves, tal y como si
resultado del select para generar un prototuvisemos una base de datos. La razn
tipo. Hacemos esto porque necesitamos
detrs de este diseo es la de unificar la
saber el formato de una fila de la tabla
manipulacin de los datos en toda la
inventario, para saber el nombre de las
aplicacin, estandarizndola. Las aplicacolumnas y sus posiciones. Qt denomina
ciones empresariales siempre tienen que
a esta informacin Record, y nos permitir
interactuar con una base de datos, por lo
realizar consultas posteriormente.
que esta decisin de diseo es cada vez
El mtodo generaModelo() es ms intems comn.
resante. Una vez realizada la conexin a
Por tanto, podemos crear nuestro
la base de datos necesitamos generar un
modelo de forma independiente, pero lo
modelo de la tabla. Todos los modelos
mejor no es eso. Lo realmente interedescienden de una clase comn, de forma
sante es que podemos conectar ese
que son intercambiables. Existen dos
modelo con una base de datos y seguiremodelos de base de datos: QSqlTableMomos manipulando los datos empleando
del, ms sencillo, y QSqlRelationalTableslo el modelo. Esto quiere decir que no
Model, que almacena tambin informatendremos apenas que escribir cdigo
cin sobre las relaciones entre distintas
SQL, el modelo se encargar de ello
tablas. Nos conformamos con QSqlTablepor nosotros. Nuestra aplicacin
Model, puesto que nuestra tabla no est
estar totalmente desligada de la
relacionada con otras. Una vez tenemos el
forma en la que se representan los
modelo, seleccionamos la tabla que quedatos. Hoy pueden estar en Postremos manipular con setTable(). Es muy
gresql, maana en MySQL y
interesante que los productos que aparezpasado en un fichero XML, y no
can en el Table View lo hagan ordenados
tendremos que cambiar apenas el
por EAN-13, lo que facilitar bsquedas y
cdigo.
comprobaciones a los usuarios. Para ello
Cmo se hace todo esto? Veaordenamos el modelo con el mtodo setmos el cdigo del Listado 1. El
Sort(). Acepta dos parmetros, la posicin
mtodo conectaDB() muestra cmo
de la columna que se emplear para orderealizar la conexin a la base de
nar y el tipo de ordenacin. Aqu entra en
datos Postgresql. El cdigo de Qt4
juego el prototipo que generamos antes
es sorprendentemente simple, comde las filas de la tabla, que posee el
parado con otras libreras. Las ltimtodo indexOf() que nos devolver la
mas dos lneas de cdigo son algo
posicin de una columna dada. La ordeextraas.
nacin puede ser QtCore.Qt.AscendingOrder (la primera fila tiene el valor ms bajo
query =
y la siguiente uno ms alto) o
QtSql.QSqlQueryU
QtCore.Qt.DescendingOrder (lo contrario).
(select * from
Con el modelo listo pasamos a cargarlo
inventario,U
con los datos, paso que realizamos invoself.db)
cando select(), que realiza un select
self.recordPrototipo =U
sobre la tabla, y almacenando los datos
query.record()
en el modelo.

W W W. L I N U X - M A G A Z I N E . E S

Qt

INTEGRACIN

realizan acciones cuando


una seal los dispara. As,
cuando se hace click sobre
un botn, la seal clicked() se pasa a un slot por
defecto del botn. Si queremos que el botn realice una
accin diferente, podemos
definir una conexin de
clicked() con una funcin
o mtodo que realice la
Figura 9: Escogemos iconos representativos de las accin que deseamos. En
C++ este proceso requera
acciones.
de la compilacin del cdigo
fuente con un compilador de Figura 10: Nuestra aplicacin en funcionamiento.
Volvemos a mirar nuestro reloj, han
TrollTech, llamado moc, y postepasado 40 minutos. Nuestros compaeriormente la compilacin con un compiNuestros compaeros se levantan para
ros se ponen nerviosos, no ven ningn
lador de C++.
ver qu estamos haciendo y casi dan un
avance.
En Python todo es ms simple, slo
salto cuando observan que la aplicacin
Conectando Mundos
tenemos que emplear el mtodo
est modificando la base de datos en
Tenemos que vincular nuestro cdigo
QtCore.QObject.connect() para especifitiempo real y de forma correcta. Nos
con la librera gui.py que generamos
car el widget y la seal que emite con el
miran y, con cara de emocin, nos espeantes. Para ello la importamos:
mtodo que la gestionar.
tan: Menos mal! creamos que todo iba
En nuestro diseo tenemos 3 botones,
fatal, como no hiciste nada til durante
from gui import Ui_MainWindow
uno para refrescar, otro para aadir
la ltima media hora. As de sufrida
lneas y otro para borrarlas. Por tanto,
es la programacin. Volvemos a mirar el
y generamos un objeto de tipo Ui_Maintenemos que conectar la seal clicked()
reloj, todo listo en menos de una hora.
Window al que nos conectamos
de estos botones a mtodos que realicen
Ahora, con ms tranquilidad nos dismediante el mtodo setupUi(), ya que es
estas acciones.
ponemos a hacer de la aplicacin algo
nuestro programa quien lleva la batuta.
La primera de ellas, refrescar, es la ms
ms potente y a bautizarla!
Pasamos a tabla nuestro modelo de
simple. Slo tenemos que decirles al
Conclusin
datos, de forma que modelo y vista tammodelo que vuelva a hacer un select()
bin se contactan. A partir de ahora lo
para recargarse.
En muy pocas lneas de cdigo, y gracias
que haya en la base de datos se corresLa segunda, nuevaLinea(), invoca al
al programa designer de Qt4, tenemos
ponder con lo que se vea en el Table
mtodo insertRow() del modelo para crear
una aplicacin multiusuario funcional
View. El mtodo reajusta() hace que el
una lnea vaca en el Table View. Cuando
que interacta con una base de datos.
Table View ajuste el ancho de sus columse rellenen todos los campos de esta linea
Qt4 ha dejado el listn muy alto para el
nas para que se puedan ver todos los
se guardar automticamente en la base
resto de frameworks de desarrollo. En
datos. En caso contrario har que las
de datos; el modelo se encarga de todo.
particular, es interesante su total integracolumnas sean todas del mismo tamao,
La tercera accin, eliminarLinea(),
cin con Windows, MacOsX y Linux,
y algunos datos aparecern incompletos.
localiza la posicin actual en la tabla
permitindonos hacer uso de nuestro
Qt posee un sistema propio de prograobteniendo el ndice de la celda que
software libre en cualquiera de las tres
macin denominado Seales y Slots. Qt
tenga el foco, y a partir de l obtiene la
plataformas de forma directa.
se desarroll en el lenguaje de programafila en la que se encuentra. Por seguridad
Qt4 es, sin duda, un opcin realmente
cin C++, y pronto se dieron cuenta de
no borraremos directamente, sino que
interesante para prototipado de aplicalas deficiencias que posea. En particular,
presentaremos un cuadro de dilogo preciones o para desarrollo rpido, bajo prese dieron de bruces con la dificultad de la
guntando al usuario si desea borrar la
sin. Al fin y al cabo ha hecho que nuesprogramacin de eventos en C++.
fila, mostrando tanto el EAN-13 como el
tro protagonista se convierta en un
Como resultado de estos problemas,
nombre del producto. Para obtener
hroe.

TrollTech, la empresa creadora de Qt,


ambos volvemos a hacer uso del protodecidi crear un compilador y una serie
tipo para conseguir sus posiciones dentro
Recursos
de instrucciones que hicieran la prograde la fila y poder recuperar sus valores.
[1] Qu es PyQt?:
macin de interfaces grficas ms simUna vez confirmada la eliminacin, slo
http://www.riverbankcomputing.co.
ple. Los detalles no son importantes, al
tenemos que emplear el mtodo removeuk/software/pyqt/intro
fin y al cabo este artculo es sobre
Row() del modelo.
[2] Listado de clases con sus explicaPython, pero el nuevo modelo de trabajo
Poco ms es necesario para que nuesciones y ejemplos: http://www.
se basa en eventos. Los objetos de Qt
tra aplicacin sea funcional, el resultado
riverbankcomputing.co.uk/static/Docs/
poseen una serie de slots o conectores a
final puede verse en funcionamiento en
PyQt4/html/classes.html
los que pueden llegar seales. Estos slots
la Figura 10.

W W W. L I N U X - M A G A Z I N E . E S

PYTHON

47

m
or
gu
ef
ile
.c
om

INFRAESTRUCTURAS

Pyramid

El framework que no fue construido por aliengenas

Pyramid
Uno de los rivales de peso de Django est creciendo en popularidad poco a poco.
Por Jos Mara Ruz

Django es el framework que ms est


contribuyendo a la extensin del uso de
Python. Al igual que ocurri con Ruby y
Ruby on Rails, Django se ha convertido
en una gran baza para la comunidad
Python y la excusa perfecta para probar
Python. Est bien documentado, dispone
de gran nmero de extensiones y el respaldo de grandes empresas por qu
querra alguien crear un competidor?
La comunidad Python dista mucho de ser
monoltica, aparecen mltiples alternativas
para casi todo. Lo curioso es que el novato en
los frameworks web es Django! Antes de su
aparicin ya existan otros distintos, y Pyramid es el descendiente directo de algunos de
ellos [1].

Un hola mundo Minimalista


Podemos instalar Pyramid de muchas formas, pero la ms cmoda desde el punto de
vista de Python consiste en crear un virtualenv e instalar en su interior Pyramid:
$ virtualenv --no-site-packages U
--distribute pruebas-pyramid
$ cd pruebas-pyramid
$ source bin/activate
(pruebas-pyramid)$ pip U
install pyramid

Con estos cuatro pasos dispondremos de un


virtualenv con las libreras que Pyramid
necesita para funcionar. Es posible crear un
proyecto Pyramid con el comando paster (un
proyecto como los que creamos con Django),
pero como vamos a generar una primera
aplicacin minimalista, slo necesitamos en
principio un fichero con el contenido que
aparece en el Listado 1.
A diferencia de Django, Pyramid es un sistema bastante estructurado y centrado en
componentes. La configuracin se efecta a
travs de una instancia de Configurator,

48

PYTHON

donde vamos aadiendo rutas, vistas y


(como ya veremos) muchos otros tipos de
componentes. Configurator es la base sobre
la que montamos nuestro sitio web.
El concepto de vista es sencillo, al igual
que en Django, pudiendo usarse una funcin
cualquiera que admita como parmetro un
objeto Request con la informacin de la peticin. Se distingue entre declarar una vista y
emplearla en distintas rutas. Cada ruta tiene
un nombre, route_name, que nos permite
conectarla con cualquier ruta. As, los mtodos add_route y add_view trabajan conjuntamente para definir el comportamiento de la
web.
Una vez hemos acabado con la
configuracin, podemos arrancar el servidor.
Para este sencillo ejemplo hacemos uso de la
funcin serve() de paste, que implementa un
servidor web en Python que acepta como
parmetro una aplicacin WSGI, que obtenemos llamando a make_wsgi_app() de Configurator.
Las rutas pueden contener parmetros en
su interior que podemos capturar de distintas
formas, como ya veremos.
Para poder arrancar el servidor slo tenemos que ejecutar el fichero como un programa Python cualquiera e ir a la direccin
127.0.0.1:8080.

Creando un Proyecto
La generacin de cdigo fuente, el llamado
scaffolding, es, a da de hoy, un elemento
indispensable de la mayora de los frameworks web. Pyramid no provee directamente un sistema de scaffolding, lo que ira
en contra de su poltica de reutilizacin. En
lugar de ello hace uso de paster, un sistema
independiente de scaffolding que comparten otros proyectos. Cuando instalamos
Pyramid con pip se instal paster como
dependencia, por lo que podemos usarlo
directamente:

$paster create --list-templates


Available templates:
basic_package: A basic U
setuptools-enabled package
paste_deploy: A web application U
deployed through paste.deploy
pyramid_alchemy: pyramid U
SQLAlchemy project using traversal
pyramid_jinja2_starter: U
pyramid jinja2 starter project
pyramid_routesalchemy:U
pyramid SQLAlchemy project using U
url
dispatch (no traversal)
pyramid_starter:pyramid starter U
project
pyramid_zodb:
U
pyramid ZODB starter project

Con la opcin --list-templates podemos ver


los proyectos que paster puede generar.
Pyramid nos ofrece varias alternativas,
desde la ms tradicional, empleando routes
y sqlalchemy, hasta otras ms exticas heredadas de Zope, como pyramid_zodb o pyramid_alchemy. La diferencia entre ambas
posibilidades est en la forma en que se
estructuran las urls y en la base de datos a
usar.
Zope permite el uso de un sistema llamado
traversal que genera automticamente las
urls empleando para ello las relaciones entre
los modelos de datos usados. La mayora de
frameworks web, en casi todos los lenguajes
de programacin, se decantan en su lugar
por la definicin de las urls de forma explcita, para as tener ms control sobre ellas.
Nosotros nos conformaremos con el enfoque tradicional, por lo que podemos crear el
proyecto con:
$ paster create -t U
pyramid_routesalchemy U
ejemplo

Como resultado obtendremos un directorio


llamado ejemplo que albergar nuestro proyecto, y en su interior un mdulo Python
llamado tambin ejemplo. Para poder arrancar el proyecto tenemos que generar primero un fichero de configuracin e instalar
los paquetes necesarios mediante el
comando:
$ cd ejemplo
$ python setup.py develop

Pyramid trae dos configuraciones: develop


para desarrollo y production para el entorno

W W W. L I N U X - M A G A Z I N E . E S

Pyramid

Renderers,
Vistas y
Plantillas
Pyramid permite
configurar
diferentes
renderers
que pueden convivir en el mismo
proyecto. Por ejemplo, podemos configurar varios sistemas de plantillas a
la vez (Mako, Chamaleon, Jinja2,)
Figura 1: Pgina por defecto de Pyramid y debug_toolbar.
y hacer que Pyramid seleccione el
de produccin. Cada una aparecer como un
correcto basndose en la extensin de la
fichero con extensin .ini que nos permitirn
plantilla a usar.
configurar el proyecto. Mientras que otros fraEn este ejemplo usaremos Jinja2, un sismeworks, como Django, prefieren que la
tema de plantillas muy parecido al empleado
configuracin se haga usando cdigo Python,
por Django pero ms flexible y potente. Prien Pyramid decidieron optar por seguir
mero tenemos que instalar la extensin de
usando ficheros de configuracin .ini.
Pyramid para Jinja2:
Una vez haya finalizado el proceso pode$ pp install pyramid_jinja2
mos arrancar el servidor web con paster:
$ paster serve U
development.ini

Podemos ver la pgina generada en la ruta


http://localhost:6543. Esta pgina incluye a la
derecha una pestaa que nos da acceso al
development toolbar de Pyramid, el cual nos
proporcionar informacin muy valiosa
durante el desarrollo de la aplicacin, as
como enlaces a la documentacin de Pyramid.

Una vez instalada debemos indicar a Pyramid que cargue la extensin y con qu ficheros queremos que use Jinja2 (en nuestro
caso los que acaben en .html). Debemos
modificar el fichero ejemplo/ ejemplo/
__init__. py, que alberga la configuracin
para nuestro proyecto, y poner dentro de
main:
config = U
Configurator(settings=settings)

INFRAESTRUCTURAS

config.include(pyramid_jinja2)
config.add_renderer(.html,U
pyramid_jinja2.rendererU
_factory)
config.add_static_view(staticU
, ejemplo:static)
config.scan()
config.add_route(portada, /)

La llamada a add_renderer() es la que nos


permite indicar a Pyramid que las plantillas
con extensin .html debern ser renderizadas
empleando Jinja2. Adems llamamos a
scan(), que se encargar de buscar las vistas
que definamos y nos ahorrar el tener que
aadirlas una a una con add_view(), como
vimos en el Listado 1. Pero para que esto sea
posible, nuestra vista debe cambiar la forma
de trabajar; debemos poner el siguiente
cdigo en ejemplo/ejemplo/views.py:
from pyramid.view import U
view_config
@view_config (route_name = U
portada, renderer = U
ejemplo:templates/portada.html)
def portada(request):
return {saludo:Hola mundo!!}

Para indicar el route_name y la plantilla que


usaremos en la vista portada usaremos el
decorador @view_config(). En l podemos
definir todos los parmetros que necesita
Pyramid para usar la vista. Con el parmetro
renderer indicamos que queremos la plantilla
portada.html, que debe estar dentro del directorio templates del mdulo ejemplo. Cada

Listado 1: Ejemplo de Pyramid Bsico


01 from paste.httpserver import serve
02 from pyramid.configuration import Configurator
03 from pyramid.response import Response
04
05 def hola_mundo(request):
06 nombre = request.matchdict.get(nombre, mundo)
07 return Response(Hola {0}!.format(nombre))
08
09 if __name__ == __main__:

10
11
12
13
14
15
16
17

config = Configurator()
config.add_route(index, /)
config.add_route(hola, /{nombre})
config.add_view(hola_mundo, route_name=hola)
config.add_view(hola_mundo, route_name=index)
app = config.make_wsgi_app()
serve(app, host=0.0.0.0)

Listado 2: Vista que Procesa Parmetros


01 from pyramid.view import view_config
02
03 @view_config(route_name=portada,
04 renderer=ejemplo:templates/portada.html)
05 def portada(request):
06 saludo = Hola mundo!!
07 if request.POST:
08 nombre = request.params. get (nombre, saludo)
09 if nombre:

W W W. L I N U X - M A G A Z I N E . E S

10 saludo = Hola {0}.format(nombre)


11
12 return {saludo: saludo}
13
14 @view_config(route_name=formulario,
15 renderer=ejemplo:templates/formulario.html)
16 def formulario(request):
17 return {}

WWW.LINUX- MAGAZINE.ES

PYTHON

49

INFRAESTRUCTURAS

Pyramid

mdulo puede disponer de sus propias plantillas independientes, lo que aumenta la


modularidad del diseo. Adems, como la
plantilla acaba en .html, Pyramid emplear el
renderer Jinja2.
Jinja2 permite establecer herencia entre
plantillas, por lo que crearemos una plantilla
ejemplo/ejemplo/templates/base.html:
<html>
<body>
<h1>Bienvenido<h1>
<hr/>
{% block contenido %}
{% endblock %}
</body>
</html>

Y otra plantilla ms llamada ejemplo/ejemplo/templates/portada.html:


{% extends ejemplo:U
templates/ base.html %}
{% block U
contenido %}
<h2>{{saludo}}U
</h2>
{% endblock %}

La extensin pyramid_
jinja2 se encarga de convertir la ruta ejemplo:templates /base. html en una
ruta del sistema de ficheros que Jinja2 pueda utilizar. Vamos a aadir una
nueva vista para demostrar cmo funcionan los
enlaces y los formularios
(ver Listado 2, Listado 3
y Listado 4).
Creamos una nueva Figura 2: El comando top funcionando en nuestro navegador.
vista que apuntamos a
procese el formulario. De esta forma podela ruta por defecto de nuestro proyecto. De
mos decidir cambiar qu vista lo procesar
esta forma la pgina principal mostrar un
siguiendo cualquier criterio que queramos,
formulario para que podamos pasar un nompuesto que la asignacin de un route_name a
bre. En el Listado 4 podemos ver el cdigo de
una vista puede variar durante la ejecucin
la plantilla ejemplo/ejemplo/templates/forde la llamada (por ejemplo, empleando critemulario.html, donde generamos la url que
rios de seguridad, o si el usuario est regisprocesar el formulario as:
trado o no).
<form action = U
En el Listado 2 podemos observar que el
{{request.route_url U
tratamiento de los datos es rudimentario.
(portada)}} method=post>
Pyramid no cuenta con una librera procesador de formularios como Django, sino que
A request.route_url() le pasamos el
dependemos del uso de una librera externa.
route_name de la vista que queremos que
Existen varias opciones posibles, pero las

Listado 3: Configuracin Necesaria para el Listado 2


01 config = Configurator(settings=settings)
02 config.include(pyramid_jinja2)
03 config.add_renderer(.html, pyramid_jinja2.renderer_factory)
04 config.add_static_view(static, ejemplo:static)

05 config.scan()
06 config.add_route(portada, /hola)
07 config.add_route(formulario, /)

Listado 4: Plantilla formulario.html


01 {% extends ejemplo:templates/base.html %}
02 {% block contenido %}
03 <form action={{request.route_url(portada)}}
method=post>
04 <p>
05
<input type=text name=nombre/>

06
<button type=submit>enviar</button>
07 </p>
08 </form>
09 {% endblock %}

Listado 5: Plantilla top.html


01 {% extends
ejemplo:templates/base.html %}
02 {% block extrahead %}
03 <script
src=http://code.jquery.com/jqu
ery-1.6.2.min.js></script>
04 <script
src=http://cdn.socket.io/stable/socket.io.js></script>
05 <script>
06 var socket = null;
07 var txt = null
08 $(function() {
09 socket = new io.Socket(null,
{});
10 socket.on(connect, func-

50

PYTHON

tion() {
11 socket.send({type: connect,
userid: 123});
12 });
13 socket.on(message, function(obj) {
14 if (obj.type == showdata) {
15 console.log(Message,
JSON.stringify(obj));
16 txt = obj.txt;
17 $(#htop).html(txt);
18 }
19 });
20 socket.connect();
21 });

22 </script>
23 <style>
24 #htop {
25 font-family: monospace;
26 font-size: 12pt;
27 background: black;
28 color: green
29 }
30 </style>
31 {% endblock %}
32 {% block contenido %}
33 <h2>htop</h2>
34 <pre id=htop ></pre>
35 {% endblock %}

W W W. L I N U X - M A G A Z I N E . E S

INFRAESTRUCTURAS

Pyramid

ms conocidas son FormEncode y FormAlchemy [5] [6] . En nuestro caso he decidido


procesar a mano la peticin.

Un Ejemplo Ms Potente
De verdad compensa la flexiblidad que nos
aporta Pyramid? Con Django es todo mucho
ms sencillo, puesto que las decisiones sobre
qu libreras emplear ya han sido tomadas.
Adems, todas las libreras estn controladas
por el proyecto, por lo que su integracin es
perfecta.
Personalmente creo que Pyramid
comienza a rendir cuando necesitamos hacer
cosas que no son tradicionales. Uno de los
problemas de Django consiste en que fue
diseado en un entorno muy bien definido:
un peridico. Django no se encuentra muy
bien preparado para el entorno actual, donde
tecnologas como HTML5 o websockets
comienzan a ser cada vez ms importantes.
Como ejemplo final de Pyramid vamos a
crear una pgina que emplear socket.io [7]
para mandar datos a nuestro navegador en
tiempo real, lo que definitivamente no es la
tpica pgina web tradicional.
Instalamos las libreras necesarias:
pip install gevent U
gevent-websocket gevent-socketio

stas nos permitirn arrancar nuestro proyecto con un servidor basado en gevent en
lugar de usar paster, lo que nos permitir responder a consultas continuadas sin necesidad de cerrar el canal de comunicacin con
el navegador.

En el Listado 5 podemos ver el cdigo de


la plantilla htop.html. En ella cargamos tanto
jquery como la librera socket.io.js, que se
encarga de establecer un canal continuo de
comunicacin entre el navegador y el servidor. Si el navegador soporta websockets, los
usar, pero en caso contrario tratar de interactuar usando otros mecanismos. Lo mejor
de socket.io es que nos permite programar
toda la interaccin mediante mensajes.
Cuando recibimos un mensaje showdata,
cambiamos el texto de la etiqueta con id htop
por el que recibimos del servidor. Realmente
sencillo verdad? No tenemos que ser conscientes ni siquiera sobre cmo se recibe el
mensaje o cmo se procesa.
El Listado 6 muestra el cdigo donde realmente ocurre la magia. Creamos una subclase de SocketIOContext, donde conectamos y mandamos un mensaje connected al
navegador remoto. Seguidamente definimos
la funcin sendhtop, que ser ejecutada por
gevent como si fuese una hebra independiente para cada conexin que recibamos.
En dicha funcin ejecutamos htop con dos
parmetros que le indican que slo nos
muestre el estado de los procesos una vez y
pare su ejecucin. De esta manera podemos
obtener una instantnea de la situacin de
nuestro ordenador. La salida de htop la mandamos en un mensaje showdata al navegador e indicamos a gevent que espere 1
segundo.
Todo esto se ejecutar en un bucle infinito
mientras el navegador est conectado, cosa
que sabremos con el resultado de self.io.connected(). De esta forma nuestro navegador

mostrar htop como en la url


http://127.0.0.1:6543/htop como si se estuviese ejecutando en l! Es fcil imaginar las
posibilidades de esta tecnologa (ver Listado
7 para configuracin).

Conclusin
Si bien Django es el framework web dominante en Python, Pyramid puede ser una
alternativa muy interesante si necesitamos
realizar una aplicacin web que no sea tradicional. Su sistema basado en componentes
nos da acceso a libreras de alta calidad y
realmente potentes que normalmente son
un fastidio integrar. En este artculo slo
hemos rascado la superficie de Pyramid
que cuenta con libreras realmente avanzadas para autenticacin, por ejemplo pero
espero que el lector haya podido dar sus primeros pasos con el framework que no ha

sido construido por aliengenas! [8].

Recursos
[1] Pyramid: https://docs.pylonsproject.
org/projects/pyramid/dev/
[2] Pylons:
org/

https://www.pylonsproject.

[3] Turbogear: http://turbogears.org/


[4] Repoze.bfg: http://bfg.repoze.org/
[5] FormEncode: http://www.formencode.
org/en/latest/index.html
[6] FormAlchemy:
http://code.google.
com/p/formalchemy/
[7] Socket.io: http://socket.io/
[8] Pyramid, not built by aliens!: https://
pylonsproject.org/denials/pyramid.
html

Listado 6: Conexin socket.io en views.py


01 from pyramid_socketio.io import
SocketIOContext, socketio_manage
02 import gevent
03
04 class ConnectIOContext(SocketIOContext):
05 def msg_connect(self, msg):
06 self.msg(connected)
07 import subprocess
08
09 def sendtop():
10 prev = None
11 while self.io.connected():

12
13

cmd = top -b -n 1
p = subprocess.Popen(cmd,
shell=True,
stdout=subprocess.PIPE)
14 txt = p.communicate()[0]
15 self.msg(showdata, txt = txt)
16 gevent.sleep(1.0)
17
18 self.spawn(sendtop)
19
20 @view_config (route_name=
socket.io)
21 def socket_io(request):

22 print Socket.IO request running


23 retval = socketio_manage(ConnectIOContext(request))
24 return Response(retval)
25
26 @view_config(route_name=top,
27 renderer=ejemplo:templates/
htop.html)
28 def htop(request):
29 return {}

Listado 7: Configuracin Necesaria para el Ejemplo con socket.io


01 config = Configurator(settings=settings)
02 config.include(pyramid_jinja2)
03 config.add_renderer(.html, pyramid_jinja2.renderer_factory)
04 config.add_static_view(static, ejemplo:static)
05 config.scan()

W W W. L I N U X - M A G A Z I N E . E S

06 config.add_route(portada, /hola)
07 config.add_route(formulario, /)
08 config.add_route(socket.io, socket.io/*remaining)
09 config.add_route(top, /htop)

PYTHON

51

INFRAESTRUCTURAS

Django

Alexey Klementiev, Fotolia

Django Software Foundation y Django

Guitarrazos
Los creadores del proyecto Django nos hablan de la formacin de la Django Software Foundation. Y
mostramos cmo comenzar con esta infraestructura web. Por Frank Wiles
En el verano de 2005 surgi en el mundo
del cdigo abierto un nuevo web framework
[1]. Slo tres aos despus desde su publicacin, Django tiene ya el suficiente atractivo como para alentar la formacin de la
Django Software Foundation [2]. Con la formacin de la DSF, Django pasa a formar
parte de una impresionante lista de proyectos con fundacin propia, entre los que se
encuentran Apache, Perl y Python.

Qu es Django?
Django es un framework para el desarrollo
web con Python. Se trata de un juego de
libreras que permiten al desarrollador trabajar en las partes de una aplicacin que
verdaderamente importan sin tener que preocuparse por la infraestructura subyacente.
Django usa el patrn MVC como otros
muchos frameworks (Ruby on Rails y los
distintos frameworks en Perl y PHP).
Una de las funcionalidades punteras de
Django es su increble interfaz de administracin, que se construye automticamente
para nosotros. En este artculo recorremos
los pasos necesarios para la creacin de una
pequea aplicacin de tipo Twitter, con la
que veremos en accin esta interfaz de
administracin.
Son muchos los sitios web de alto nivel
que emplean Django en su desarrollo [3],
como EveryBlock.com, Pownce.com o Tabblo.com. Adems, es el framework predeterminado incluido en el AppEngine de Google

52

PYTHON

(tenemos entendido que Google lo usa tambin internamente en algunas tareas).


Django es, adems, la fundacin del gestor
de contenidos comercial Ellington, usado en
varias organizaciones de gran tamao del
mundo de las noticias, como por ejemplo el
Washington Post.
Jacob Kaplan-Moss, presidente de la
Django Software Foundation y uno de los
creadores de Django, dijo que la fundacin
fue creada con el fin de que el proyecto
pudiese dar el siguiente paso en su ciclo de
vida como proyecto de cdigo abierto.
Obviamente hemos tenido xito a la hora
de atraer a una comunidad grande, vibrante,
por lo que sentimos que era hora de que
Django perteneciese a la comunidad. Con
la fundacin se garantiza su perpetuidad,
incluso aunque algunas personas o algunas
compaas perdiesen inters, coment.
Kaplan-Moss seala que el proyecto
acepta ahora donaciones para mejorar
Django y, en un futuro prximo, la fundacin
soportar Django mediante reuniones de
desarrolladores, mtines y otras actividades
comunitarias. Muchas de estas reuniones de
desarrolladores han tenido lugar antes de la
publicacin de Django 1.0 -- y Kaplan-Moss
comenta que la fundacin ayudar a que las
personas ms indispensables puedan colaborar al tiempo que asisten a las reuniones. Si
la fundacin ayuda a Django a avanzar, aunque slo sea un poco ms rpido, con eso me
bastar, dijo Kaplan-Moss.

Comencemos
La publicacin de la versin 1.0 oficial de
Django se produjo el da 2 de Septiembre de
2008, tal y como estaba planeado en su hoja
de ruta.
Siempre podemos obtener, usando Subversion, el cdigo ms reciente:
svn checkout U
http://code.djangoproject.com/U
svn/django/trunk/

Independientemente de la versin que usemos, la instalacin de Django es muy sencilla. Estando conectados a Internet, ejecutamos como root:
python setup.py install

para instalar Django en el directorio sitepackages o donde sea que tengamos la instalacin de Python. En nuestro ejemplo usaremos SQLite como base de datos. De todas
formas, Django soporta perfectamente PostgresSQL y MySQL.
Para usar SQLite, instalamos el paquete
pysqlite2 [5] y seguimos las instrucciones de
la instalacin.
Django distingue entre proyectos y aplicaciones. Por ejemplo, si hicisemos un sitio
web de gran tamao, con una seccin formada por un blog, un foro o comercio
online, entonces el sitio en s sera el proyecto, mientras que el blog, el foro y el e-

W W W. L I N U X - M A G A Z I N E . E S

Django

Figura 1: La interfaz de administracin de Django.

comercio seran las aplicaciones. En realidad


slo es una forma de organizar los subproyectos dentro del proyecto general.
Para iniciar un nuevo proyecto ejecutamos
django-admin.py startproject U
miprueba

con lo que se crea el directorio miejemplo


con unas pocas herramientas y archivos de
configuracin predeterminados. Luego
necesitamos que Django genere los de la
aplicacin, que llamaremos Prueba. Para
ello, ejecutamos desde el directorio
miprueba:
python manage.py startapp Prueba

En miprueba/settings.py, colocamos DATABASE_ENGINE


=
sqlite3
y
a
DATABASE_NAME le ponemos la ruta com-

Figura 2: Para aadir informacin a nuestra aplicacin personal.

pleta a miprueba/prueba.db, el archivo de


SQLite en el que vamos a guardar nuestra
base de datos. La ruta completa depende del
directorio en el que hemos ejecutado el
startproject inicial. Aadimos dos elementos
a la lista INSTALLED_APPS: django.contrib.admin para la interfaz de administracin y la aplicacin miprpueba.Prueba, asegurndonos de que aadimos las comas.
Despus de definir qu base de datos
vamos a usar, tenemos que construir nuestro modelo (Model), que es un objeto de
Python que define las tablas y columnas de
SQL y su relacin. Puesto que la aplicacin
slo va a tener una tabla, slo definimos
una clase. El archivo miprueba/Prueba/
models.py debera parecerse al que se muestra en el Listado 1.
Primero importamos los asistentes de
modelo de Django y definimos la clase
Prueba, que contiene una columna de fecha
y otra de texto para la entrada real. Luego

Listado 1: miprueba/Prueba/models.py
01
02
03
04
05
06
07
08

from django.db import models


class Prueba(models.Model):
fecha = models.DateField(Date`)
entrada = models.CharField( max_length=`500` )
def __unicode__(self):
return %s %s` % (self.fecha, self.entrada)

Listado 2: Invertimos las Entradas


01
02
03
04
05
06

from django.shortcuts import render_to_response


from models import Prueba
def todas_las_pruebas(request):
all_entries = Twit.objects.all().order_by(fecha).reverse()
return render_to_response(todas_las_pruebas.html,{entradas:
todas_las_entradas })

W W W. L I N U X - M A G A Z I N E . E S

INFRAESTRUCTURAS

WWW.LINUX- MAGAZINE.ES

definimos el mtodo especial __unicode__,


que le dice a Model cmo mostrar una instancia del objeto en formato de cadenas (en
este caso, slo imprime la fecha y la entrada
completa). Esta ser la informacin que
usar el admin en el momento de mostrar
los listados con las entradas de la base de
datos. La clase vaca Admin indica a Django
que queremos hacer uso de la interfaz de
administracin.
Para comprobar lo que llevamos hecho,
validamos los modelos mediante:
python manage.py validate

Si todo est bien, debera devolvernos 0


errors found. Django ya puede montar las
tablas de la base de datos. Para ello, introducimos
python manage.py syncdb

Vemos varias lneas con Creating table,


algunas de las cuales pertenecen a permisos de usuario/grupo, otras a admin y la
ltima para la tabla Prueba. Es ahora
cuando Django nos insta a crear el superusuario para la interfaz de administracin.
Debemos recordar el nombre de usuario y
la contrasea, ya que las vamos a necesitar
luego.
Despus de crear las tablas para el
modelo y la base de datos, habilitamos la
interfaz de administracin. Lo hacemos descomentando las tres lneas del archivo
miprueba/urls.py que se cre al ejecutar
startproject. Las tres lneas estn etiquetadas, indicndonos que hay que descomentarlas para que se habilite la administracin.
El archivo urls.py es con el que Django,
mediante expresiones regulares, enlaza las
diferentes URLs con las distintas partes de
nuestra aplicacin.

PYTHON

53

INFRAESTRUCTURAS

Django

Listado 3: Hacemos la Plantilla


01
02
03
04
05
06
07
08
09
10

<html>
<body>
<table>
<tr>
<th>Fecha</th>
<th>Entrada</th>
</tr>
{% for t in entradas %}
<tr>
<td>{{ t.fecha
}}</td>
<td>{{
t.entrada}}</td>
</tr>
{% endfor %}
</table>
</body>
</html>

diferentes, podemos especificarlo en el


comando:
python manage.py U
runserver 127.0.0.1:5555

Suponiendo que lo hayamos ejecutado sin


dicha opcin, nos dirigimos a http://127.0.0.
1:8000/admin, donde se nos instar a identificarnos para acceder a la interfaz de administracin. Despus de identificarnos, veremos la pantalla que se muestra en la Figura
1.
11
Debido a que estamos haciendo una aplicacin personal, podemos ignorar por ahora
12
las secciones Sites y admin y pulsar sobre el
13
icono Add del cuadro Prueba. Veremos
14
entonces algo como lo que se muestra en la
15
Figura 2.
16
Ya podemos introducir datos de entrada.
Al pulsar sobre Today se rellena automticaAdems, debemos crear un archivo
mente la fecha actual, aunque podemos
admin.py. En este ejemplo slo usaremos la
usar el calendario para escoger otra. Lo
configuracin predeterminada, pero ste es
siguiente es introducir texto en Entrada y
el lugar en el que se pueden personalizar
pulsar sobre Save, que nos lleva a una
varios de los aspectos de la interfaz de admipgina que nos muestra todas las Pruebas de
nistracin.
miprueba/Prueba/admin.py
nuestra base de datos. Al pulsar sobre la
necesitar contener:
entrada que acabamos de hacer, llegamos a
una interfaz de edicin, con la que podrefrom django.contrib import admin
mos realizar cambios sobre ella o eliminarla.
from miprueba.Prueba.models U
Es posible compartir las entradas de la
import Prueba
web con otras personas. Para poder hacerlo
class PruebaAdminU
debemos aadir una vista (o mdulo encar(admin.ModelAdmin):
gado de la lgica) y una plantilla (la forma
pass
en que se presentan los datos al usuario).
admin.site.registerU
Aquellos que estn acostumbrados a otros
(Prueba, PruebaAdmin)
frameworks de tipo MVC, en los que la vista
suele ser la plantilla en s misma, pueden
Para verlo en accin, ejecutamos
tener un poco ms de dificultades a la hora
de acostumbrarse a las vistas de Django.
python manage.py runserver
Para comenzar, editamos miprueba/
Prueba/views.py, de forma que contenga un
que arranca un servidor de pruebas en la
simple mtodo para devolver todas las
direccin http://127.0.0.1:8000. Para inientradas en orden cronolgico inverso (Lisciarlo en una direccin IP o en un puerto
tado 2). As definimos el mtodo TodasLasPruebas, que recoge todos los
objetos Prueba, ordenados por el
Listado 4: Mapear a URL
campo de fecha, y los invierte.
01 from django.conf.urls.defaults import *
Luego llama a render_to_res02 from django.contrib import admin
ponse(), con el nombre de la plan03 from mytwit import views
tilla para la vista y un diccionario
04
con los datos que le queremos
05 admin.autodiscover()
pasar.
06
Cuando hayamos acabado con
07 urlpatterns = Patterns(,
la vista, tendremos que hacer la
08
(r^twits/.
plantilla. El Listado 3 muestra un
mytwit.Twit.views.alltwits).
ejemplo de marcado simple con el
09
(radmin/(.*), admin.site.root),
que captar la idea general. Con el
10 )
fin de mantener las plantillas sepa-

54

PYTHON

radas de todo lo dems, guardamos el


archivo
en
miprueba/plantillas/
todas_las_pruebas.html.
Como puede apreciarse, el lenguaje de
plantillas de Django es fcil de usar, aunque
tiene funcionalidades avanzadas. En sta
usamos un bucle for simple, con el que iteramos sobre los objetos Prueba que vamos a
pasar al mtodo TodasLasPruebas() y mostrar los datos de cada objeto Prueba
mediante sus mtodos fecha y entrada.
Luego configuramos Django para que
encuentre nuestra plantilla en el sistema de
archivos, e instalamos una URL que enlace
con la vista. Para instalar los directorios de
plantillas en miprueba/settings.py hemos de
aadir la ruta completa a la lista TEMPLATE_DIRS, que depender del directorio
desde el cual se ejecut startproject (debemos asegurarnos de utilizar la ruta completa). Despus editamos miprueba/urls.py
para mapear la URL (Listado 4).
As hemos importado las vistas especficas de la aplicacin, hemos aadido la URL
/pruebas/ y hemos dejado como estaba el
mapeo de la interfaz de administracin. Si
nos dirigimos a http://127.0.0.1:8000/
pruebas, veremos todas las Pruebas que
hemos aadido desde la interfaz de administracin.
El binomio formado por el servidor independiente y SQLite es genial para el
desarrollo rpido, pero si lo que queremos
es montar una aplicacin en produccin es
mejor usar Apache con mod_python, junto
con una base de datos ms robusta, como
PostgreSQL (ver el sitio web de Django).
Nuestro agradecimiento a Jacob KaplanMoss y a Adrian Holovaty por su contribucin a este artculo. Los listados de cdigo se

pueden descargar de [7].

Recursos
[1] Sitio Web del proyecto Django:
http://www.djangoproject.com
[2] Django Software Foundation: http://
www.djangoproject.com/foundation
[3] Sitios desarrollados con Django:
http://www.djangosites.org
[4] Descarga de Django: http://www.
djangoproject.com/download/
[5] Pysqlite2: http://initd.org/pub/
software/pysqlite/
[6] Django Book:
http://www.djangobook.com
[7] Cdigo del Artculo:
http://www.linux-magazine.es/
resources/article_code

W W W. L I N U X - M A G A Z I N E . E S

Thrift

Serializacin estilo Web 2.0

Benis Arapovic - 123rf.com

Hacer que distintos servicios se comuniquen entre ellos es todo un problema que Facebook
ha tratado de solucionar con Thrift. Por Jos Mara Ruz
La informacin quiere ser libre es uno
de los lemas hacker, pero habra que preguntarse cmo se mover una vez que lo
sea. En un mundo como el actual, donde
la informacin y su procesado pueden
encontrarse distribuidos en decenas (o
miles!) de servidores, es preciso contar
con algn mecanismo que permita comunicar sistemas informticos de todo tipo
sin tener que recurrir a tecnologas de
bajo nivel. Este problema ha sido resuelto
por muchos mediante el empleo del protocolo HTTP, pero cuando lo que buscamos es rendimiento, HTTP puede no
ser una opcin.
No es de extraar que dos de las empresas de referencia de Internet, Facebook y
Google, se hayan enfrentado a este mismo
problema y hayan creado, de forma totalmente independiente, dos tecnologas que
buscan solucionarlo: Thrift [1] y Protocol
Buffers [2].
Tanto Facebook como Google necesitaban una forma de poder comunicar sistemas informticos desarrollados en diferentes lenguajes de programacin para
que pudiesen intercambiar datos entre
ellos. Por ms que queramos a nuestro
querido Python, cuando el rendimiento es
una prioridad, no es nuestra mejor
opcin. Y puede ocurrir tambin lo contrario: por mucho que nos guste Java, en
numerosas ocasiones la velocidad de
desarrollo con Python no tiene rival!
En este artculo echaremos un vistazo a
la tecnologa Thrift de Facebook y vere-

W W W. L I N U X - M A G A Z I N E . E S

mos cmo Python puede usarla para


comunicarse con un sistema Java que
est ganando gran popularidad: ElasticSearch.

Serializando
Al acto de transformar un formato interno
de datos en algo que podamos transmitir
o almacenar de forma externa se le suele
denominar serializar. Existe una cantidad
inimaginable de formatos que podemos
usar para serializar datos, y Python viene
de serie con varias opciones:
XML
JSON
Pickle
Sqlite
Todos ellos cuentan con ventajas e inconvenientes.
XML es uno de los formatos ms extendidos del mundo. Al ser un formato de
texto es fcilmente manipulable, y es
posible editarlo a mano. Su nivel de complejidad es seleccionable, podemos hacer
que sea tan complejo y almacene tanta
informacin como deseemos.
JSON fue la respuesta de la Web a la
complejidad de XML. Al igual que l, es
un formato de texto, pero su estructura
est cerrada. Es sorprendentemente simple y sencillo de manejar, lo que no
ayuda demasiado en situaciones complejas.
Pickle es en realidad un mecanismo
propio de Python para la superlacin de
objetos. Ningn otro lenguaje parece dis-

INFRAESTRUCTURAS

poner de soporte para l, y cuenta con al


menos dos versiones. Cualquier objeto
serializable en Python puede serializarse
con Pickle y recuperarse de nuevo intacto.
Aunque parezca una burrada, Sqlite
puede considerarse un formato de serializacin. En principio no existe nada que
nos impida usar bases de datos Sqlite
como formato para transportar nuestros
datos entre aplicaciones. Ser lento, pero
funcionar.
Todos estos formatos tienen algn tipo
de problema, ya sea la complejidad de
XML, la extrema simplicidad de JSON, la
falta de interoperabilidad de Pickle o la
lentitud de usar Sqlite. Cuando la serializacin es vital para el funcionamiento de
nuestros sistemas, necesitamos algo ms
potente.

Instalacin de Apache Thrift


Thrift es un protocolo de serializacin
binario y tipado que define tanto datos
como servicios (ver Figura 1). Al ser binario es mucho ms eficiente que los formatos de texto, y puede enviar la misma cantidad de informacin de forma ms compacta, ahorrando ancho de banda. Adems est tipado, lo que significa que toda
informacin que se transmita ser de un
tipo de dato determinado. Esto es imprescindible si queremos ser capaces de intercambiar datos con lenguajes tipados
como C#, Java o C++.
Podemos descargar el cdigo fuente de
Thrift desde el enlace que aparece en el
Recurso 1. Para poder compilar Thrift
necesitaremos disponer de flex, as como
de la librera de desarrollo Boost en nuestro sistema (en Ubuntu necesitaremos el
paquete libboost-dev, por ejemplo). Una
vez la tengamos instalada deberemos descomprimir el fichero, compilarlo e instalarlo. Es recomendable ejecutar el configure indicando los lenguajes para los que
no queremos generar un compilador,
puesto que tratar de generar los compiladores de todos los lenguajes:
shell$ ./configure U
-without-javaU
--without--csharp
...
shell$ make
shell$ sudo make install

Podemos comprobar que se ha instalado


correctamente ejecutando el compilador
de Thrift:

PYTHON

55

INFRAESTRUCTURAS

Thrift

Ya tenemos todo lo necesario para usar


Thrift, pasemos a usarlo.

Hola Mundo

Figura 1: Esquema de trabajo con Thrift.

shell$ thrift
Usage: thrift [options] file
Options:
-version Print the ?.
....

Necesitamos un componente ms para


poder hacer uso de Thrift: la librera
python thrift. Instalarla es mucho ms
sencillo:
shell$ pip install thrift

Thrift funciona como un compilador


(Figura 2) que acepta una descripcin de
los datos y servicios, la interfaz, que
vamos a utilizar, y genera a partir de ella
una librera en el lenguaje de destino que
codificar y decodificar ese formato en
el lenguaje de programacin que le indiquemos. Es ms sencillo verlo con un
ejemplo. Digamos que queremos disponer de un servicio llamado hola que
acepta una cadena de texto y devuelve
otra cadena de texto. Lo primero que
necesitaremos es crear un fichero de descripcin en el formato de Thrift como el
que aparece en el Listado 1. Una vez tengamos el fichero listo, podemos generar
el cdigo Python con el siguiente
comando:
shell$ thrift -gen pyU
hola.thrift

El comando generar un directorio llamado gen-py que contendr el mdulo


que vamos a usar. Debemos copiar el
mdulo hola al directorio en el que vayamos a ejecutar tanto el script del Listado 2
como el del Listado 3. El cdigo del Lis-

Listado 1: Fichero hola.thrift


01 service Saludos {
02
string hola(1: string
nombre)
03 }

tado 2 genera un servidor de red que responder a la interfaz declarada en el


fichero hola.thrift, mientras que en Listado 3 se encuentra el cdigo que usar
este servicio. Para verlos en funcionamiento tendremos que arrancar primero
el servidor en un terminal:
shell$ python servidor.py
Arrancando el servidor...

Y el cliente en otro terminal:


shell$ python cliente.py
Hola mundo
shell$

Ha funcionado! Thrift nos permite arrancar un servidor de red con el servicio que
hemos definido. La librera se encarga de
prcticamente todo, lo que nos permite
concentrarnos en crear el cdigo fuente
de nuestro servicio. Pero es Thrift
rpido? Hagamos una prueba. Con el servidor an arrancado, vamos a mandar

Listado 2: Fichero servidor.py


01 #!/usr/bin/env python
02
03 import sys
04
05 from hola import Saludos
06 from hola.ttypes import *
07
08 from thrift.transport import
TSocket
09 from thrift.server import TServer
10
11 ## Servicio
12 class SaludosHandler:

13 def hola(self,nombre):
14 return Hola {0}.format (nombre)
15
16 ## Pasos necesarios para arrancar
17 ## el servidor
18 handler = SaludosHandler()
19 processor =
Saludos.Processor(handler)
20 transport = TSocket.TServerSocket(9090)
21 tfactory = TTransport.TBufferedTransportFactory()

22 pfactory = TBinaryProtocol. TBinaryProtocolFactory()


23
24 ## Arrancamos
25 servidor = TServer.TSimpleServer(processor, transport,
tfactory, pfactory)
26
27 print Arrancando el servidor...
28 servidor.serve()
29 print acabamos.

10
11 try:
12 transport = TSocket.TSocket
(localhost, 9090)
13 transport = TTransport.
TBufferedTransport(transport)
14 protocol = TBinaryProtocol.TBinaryProtocol(transport)
15
16 cliente = Saludos.Client(protocol)
17

18 ## Conectamos
19 transport.open()
20
21 cadena = cliente.hola(mundo)
22 print cadena
23
24 ## Cerramos la conexin
25 transport.close()
26 except Thrift.TException, tx:
27 print %s % (tx.message)

Listado 3: Fichero cliente.py


01 import sys
02 from hola import Saludos
03 from hola.ttypes import *
04 from hola.constants import *
05
06 from thrift import Thrift
07 from thrift.transport import
TSocket
08 from thrift.transport import
TTransport
09 from thrift.protocol import TBinaryProtocol

56

PYTHON

W W W. L I N U X - M A G A Z I N E . E S

Thrift

10000 mensajes mediante el cdigo del


Listado 4:
shell$ time python U
test_velocidad.py
real 0m1.715s
user 0m0.820s
sys 0m0.156s

No est nada mal, siendo Python, y sin


usar ninguna optimizacin. Podemos
parar el servidor pulsando la combinacin de teclas Control+C.

Cmo Funciona el Cdigo Fuente


Analicemos el cdigo del Listado 2. Cargamos el mdulo hola resultado de nuestra
definicin en el fichero hola.thrift, y que
se encontraba dentro del directorio gen-py.
Este mdulo es en realidad el nombre del
propio fichero, que Thrift ha convertido en
mdulo, por lo que hay que tener cuidado
con el nombre que demos al fichero .thrift.
Dentro del fichero hemos definido un servicio llamado Saludos. Thrift nos permite
reunir grupos de funciones y variables
bajo servicios. As es muy sencillo organizar nuestro cdigo. Pasamos a cargar el
fichero ttypes que contiene todas las funciones, objetos y variables que necesitaremos para usar Thrift.
Nuestro servicio ser un objeto Python
con mtodos que tengan los mismos
nombres y parmetros que definimos en
el fichero hola.thrift. Por convencin se
aade la palabra Handler al nombre del
servicio que vamos a implementar, en
nuestro caso SaludosHandler.
Mediante el mtodo Saludos.Procesor(),
indicamos qu objeto implementar la
interfaz definida. Al objeto resultante se
le suele llamar processor, puesto que su
funcin ser procesar peticiones.
En este punto podemos elegir cmo
vamos a usar nuestro procesador. Thrift
debe trabajar sobre algn protocolo deco-

municaciones, ofreciendo varias posibilidades dependiendo del lenguaje de programacin que usemos. En Python es
posible usar un socket, el protocolo http o
Twisted. Por simplicidad vamos a usar un
socket, que es el protocolo de ms bajo
nivel, mediante la clase TSocket. Sobre el
protocolo de comunicaciones debemos
montar un servidor, TServer, que atienda
los mensajes que lleguen y se los pase al
procesador. Como puedes ver, los nombres son bastante descriptivos.
Thrift nos obliga an a hacer algunas
elecciones. Debemos indicar al servidor
qu clase de protocolo vamos a usar en
nuestro caso TBinaryProtocol y cmo
queremos que se comporte el servidor,
usando un bfer con TBufferedTransport.
Thrift es configurable y nos ofrece diferentes opciones para casi todo. Podramos
haber seleccionado un protocolo basado
en JSON mediante TJSONProtocol, por
ejemplo.
Ya slo nos falta arrancar el servidor,
instanciando por ejemplo TSimpleServer y
llamando al mtodo server() que se bloquear mientras no lleguen mensajes.

ElasticSearch
Como ejemplo del uso de Thrift vamos a
crear un pequeo programa Python que
emplee esta tecnologa para interactuar
con el motor de bsqueda de moda: ElasticSearch [3]. ElasticSearch est revolucionando el mundo de los motores de
bsqueda. Ofreciendo el rendimiento de
Solr/Lucene, pero aadiendo la capacidad
de trabajar de forma distribuida, reparte
el ndice de bsqueda entre varias mquinas de forma automtica. Est programado en Java y ofrece varios protocolos
de trabajo, siendo posible comunicarse
con el servidor ElasticSearch mediante
Rest sobre http o Thrift (Figura 3).
Para instalar ElasticSearch slo tenemos que descargarlo desde la direccin
del Recurso 3 :

INFRAESTRUCTURAS

Figura 2: Http vs Thrift.

shell$ wget -c U
https://github.com/downloads/U
elasticsearch/elasticsearch/U
elasticsearch-0.16.2.tar.gz
shell$ tar zxpf U
elasticsearch-0.16.2.tar.gz
shell$ cd elasticsearch-0.16.2
shell$ cd bin
shell$ ./plugin -install U
transport-thrift
shell$ ./elasticsearch -f

Listo! Ya tenemos funcionando un motor


de bsqueda con ndice distribuido y que
se comunica usando Thrift. Es normal
que ElasticSearch se est ganando el corazn de muchos desarrolladores. Debemos
generar el mdulo de la interfaz de Thrift
para Python. Para ello debemos descargar
el fichero elasticsearch.thrift de la direccin que aparece en el Recurso 4. Y compilarlo:
shell$ thirft --gen U
py elasticsearch.thrift

Cuando tengamos el directorio gen-py,


extraemos de su interior el mdulo elas-

Listado 4: Test de Velocidad


01 import sys
02 sys.path.append(./gen-py)
03
04 from hola import Saludos
05 from hola.ttypes import *
06 from hola.constants import *
07
08 from thrift import Thrift
09 from thrift.transport import
TSocket
10 from thrift.transport import

W W W. L I N U X - M A G A Z I N E . E S

TTransport
11 from thrift.protocol import TBinaryProtocol
12
13 transport =
TSocket.TSocket(localhost,
9090)
14 transport =
TTransport.TBufferedTransport(t
ransport)
15 protocol = TBinaryProtocol.TBi-

naryProtocol(transport)
16
17 cliente = Saludos.Client(protocol)
18
19 transport.open()
20
21 for i in range(0,10000):
22 cadena = cliente.hola(mundo)
23
24 transport.close()

PYTHON

57

INFRAESTRUCTURAS

Thrift

ticsearch y lo ubicamos en el mismo directorio en el que pongamos el script del Listado 5. Cuando lo ejecutemos, ste ser el
resultado:
shell$ time python busqueda.py
[{u_score: 0.38431653, U
u_type: uarticulo, U
u_id: u1,
u_source: {utitulo: U
uThrift, Python y U
ElasticSearch},
u_index: ulinuxmagazine}]
real 0m0.353s
user 0m0.040s
sys 0m0.016s

Hemos creado un ndice, aadido un


modelo de documento, insertado un
documento y realizado 100 bsquedas en
300 milisegundos. ElasticSearch trabaja
usando una API Rest que acepta comandos codificados en URIs (rutas) mediante
los mtodos tpicos de HTTP. Tanto los
datos enviados como los recibidos se
codifican en JSON, que podemos codificar y decodificar empleando la librera
json de Python.
El esquema de trabajo es parecido al
que hemos visto con anterioridad. Creamos una conexin usando TSocket, especificamos el tipo de transporte y el protocolo (en este caso una variante del binario) y generamos un cliente.
La clase RestRequest ha sido generada
por Thrift y tiene cuatro parmetros:

Mtodo (POST, GET, PUT)


URI(<indice>/<modelo>/<id>.)
Cabeceras (Headers)
Cuerpo del mensaje (Body)
Algunos mtodos exigen el uso del body
(por ejemplo los que requieren el mtodo
POST), mientras que otros slo requieren
el URI (GET) Y por qu usamos nmeros
para el tipo de mtodo usado? Si echamos
un vistazo al fichero elasticsearch.thrift
veremos que ah se declaran los nmeros
que usaremos para los mtodos.
En nuestro ejemplo podemos ver diferentes mtodos en uso. Crear un ndice
exige un POST, aadir un modelo, un
PUT y hacer una consulta, un GET. Cada
llamada de ejecucin de un request
devuelve un objeto RestResponse con un
campo body, en el que encontraremos el
resultado codificado en JSON.

Conclusin
Thrift puede parecer algo complejo ahora
que todos nos hemos acostumbrado a
emplear HTTP como protocolo para las
peticiones remotas. Pero existen muchas
situaciones en las que necesitaremos utilizar un protocolo que consuma menos
ancho de banda y ofrezca ms rendimiento. Tanto Facebook como Google
han tenido que desarrollar su propia tecnologa para solventar este problema, y
ambos han tenido la gentileza de liberarla como software libre. Y por si fuese
poco, ambos sistemas generan cdigo
Python, todo un regalo para nuestra
comunidad.

Figura 3: Funcionamiento de ElasticSearch.

Recursos
[1] Tecnologas Thrift de Facebook:
http://thrift.apache.org/
[2] Tecnologa Protocol Buffers de Google:
http://code.google.com/p/protobuf/
[3] Motor de bsqueda ElasticSearch:
http://www.elasticsearch.org/
[4] Fichero thrift para ElasticSearch:
https://github.com/elasticsearch/ elasticsearch/blob/master/plugins/ transport/thrift/elasticsearch.thrift

Listado 5: Interactuando con ElasticSearch


01 from thrift import Thrift
02 from thrift.transport import
TTransport
03 from thrift.transport import
TSocket
04 from thrift.protocol.TBinaryProtocol import TBinaryProtocolAccelerated
05
06 from elasticsearch import Rest
07 from elasticsearch.ttypes import
*
08
09 import json
10
11 socket = TSocket.TSocket(localhost, 9500)
12 transport =
TTransport.TBufferedTransport(s
ocket)
13 protocol = TBinaryProtocolAccelerated(transport)
14 client = Rest.Client(protocol)
15

58

PYTHON

16 transport.open()
17
18 ## Creamos un ndice
19 request = RestRequest(method=1,
uri=/linuxmagazine,
20
headers={}, body=)
21 client.execute(request)
22
23 ## Cargamos un modelo de documento
24 mapping = json.dumps({properties: {
25
titulo : {type : string,
store : yes}}})
26 request = RestRequest(method=2,
uri=/linuxmagazine/articulo,
27
headers={}, body= mapping)
28 client.execute(request)
29
30 ## Cargamos un documento
31 articulo = json.dumps({titulo :
Thrift, Python y ElasticSearch})
32 request = RestRequest(method=2,
33
uri=/linuxmagazine/artic-

ulo/1,
34
headers={},
35
body= articulo)
36 respuesta =
client.execute(request)
37
38 ## Buscamos la cadena thrift
39 ruta = /linuxmagazine/articulo/_search?q=thrift
40 for i in range(0, 100):
41 request = RestRequest(method=0,
42
uri=ruta,
43
headers={},
44
body= )
45 respuesta =
client.execute(request)
46
47 print
json.loads(respuesta.body)[hit
s][hits]
48
49 transport.close()

W W W. L I N U X - M A G A Z I N E . E S

Curses

LIBRERAS

La librera curses en Python

Cuaderno de
Bitcora

Te acuerdas de cuando cambiaste la versin de Firefox por ltima vez? Y de por qu instalaste ese programa tan raro que parece no servir
para nada ? Yo tengo mala memoria, as que uso un cuaderno de bitcora. Por Jos Mara Ruz y Pedro Orantes
Cuaderno de bitcora, fecha estelar
2123.
En todos los libros sobre administracin de sistemas se nos recomienda llevar
un pequeo cuaderno de bitcora donde
ir reflejando las acciones peligrosas que
realicemos. De esta manera, se supone,
podremos recrear paso a paso los eventos
que nos llevaron a un desastre y por tanto
ir deshacindolos en orden inverso.
La cruda realidad es que no todo el
mundo usa dichos cuadernos. Es pesado
tener que dejar el teclado y coger el bolgrafo para escribir a mano! no estbamos en la era de los ordenadores? No
bamos a desterrar el papel?
Muchas personas usan un weblog en su
propia mquina o en Internet para ir
apuntando detalles o noticias que le resul-

W W W. L I N U X - M A G A Z I N E . E S

tan de inters. Mucha gente incluso publica sus ficheros de configuracin, de


manera que siempre pueda acceder a
ellos.
Y que ocurre si solo lo queremos para
nosotros? Y si la mquina a la que estamos accediendo no tiene un servidor web
con el software adecuado configurado
para tener un weblog? y si no queremos
montar tanta parafernalia?
Algunas aplicaciones, como KPIM,
incorporan ya la opcin de llevar un diario personal, pero no funcionan de forma
remota a no ser que tengamos una conexin de red con mucho ancho de banda.
Qu opciones nos quedan? Podemos
volver nuestra mirada a la era antigua de
los ordenadores, cuando los interfaces
funcionaban exclusivamente desde una

consola de texto. Dichos interfaces an se


utilizan en numerosas aplicaciones, la
razn es que son mucho ms simples de
usar. Es ms fcil automatizar el pulsar
tres veces TAB que mover el ratn y funcionan mejor remotamente, an con
conexiones lentas.
Vamos a disear y programar un cuaderno de bitcora en Python, que utilizar
ncurses para el interfaz texto y dbm para
almacenar las entradas por fecha.

Diseo del Cuaderno


Comencemos nuestro diseo echando un
vistazo a las libreras en que nos vamos a
basar. ncurses fue desarrollada para abstraer, ocultar y simplificar la gestin de
terminales texto. Cada fabricante dotaba
a su terminal de texto de caractersticas

PYTHON

59

LIBRERAS

Curses

Figura 1: Hola Mundo en nuestro primer programa curses.

distintas a las del resto, forzadas la mayora de las veces por una feroz competencia. Esto converta en una tortura el simple hecho de cambiar un terminal por
otro, requiriendo la mayora de las veces
la modificacin del programa de turno.
ncurses permita realizar programas sin
tener en cuenta las diferencias entre los
terminales. No solo eso, sino que adems
simplific enormemente la gestin de
interfaces de texto como veremos ms
adelante.
dbm es una base de datos. Lo pongo
entre comillas porque en realidad slo
nos permite almacenar datos, recuperarlos y realizar bsquedas, pero no usando
SQL sino llamadas a libreras. <dbm> es
una familia de libreras que nos permiten
almacenar datos en un fichero y gestionarlos como si fuesen un diccionario o
hash en Python. Cada entrada se compone de una clave y un valor asociado. Si no
tenemos que realizar bsquedas complejas, dbm se convertir en nuestro mejor
opcin.

Bsicamente tenemos que mostrar un


interfaz que divida la pantalla en dos partes. En una deber mostrar las fechas
almacenadas, y debe permitir recorrerlas.
En la otra debe mostrar el texto relacionado con la fecha indicada.
Las acciones sern:
Navegar entradas.
Crear entrada.
Editar entrada.
Borrar entrada.
Salir.
Cada una de las acciones se corresponder con una combinacin de teclas.
Comenzaremos creando los objetos que
gestionen los datos y posteriormente el
interfaz con el usuario.

Almacenamiento de datos
Debemos conservar el texto asociado a
una fecha y hora en algn sitio. Con la
fiebre actual por las bases de datos relacionales pocas veces se menciona la
existencia otras bases de datos que no
cumplen el estndar relacional ni SQL.
Realmente se necesita un motor relacional y SQL para cualquier cosa que
necesitemos almacenar? Por supuesto
que no. Desgraciadamente, cuando slo
tienes un martillo, todo te parece clavos.
El problema est en la definicin de
base de datos, dbm lo es pero sin
mucha sofisticacin. Bsicamente nos

Figura 2: Un cuadro de texto curses.

permite almacenar claves y valores asociados a las mismas, as como recuperar


el valor o borrar las claves. Ni ms, ni
menos.
La librera dbm necesita un fichero
donde depositar los datos que se almacenan. As, tendremos que darle el
nombre de un fichero e indicarle como
queremos que lo trate. Puede abrir el
fichero para introducir nuevos datos o
crearlo de nuevo, aunque ya exista uno
con el mismo nombre.
Una vez abierto el fichero, un objeto
dbm se comporta como un contenedor
cualquiera. Podremos hacer uso de la
sintaxis [] a la que nos tienen acostumbrados la mayor parte de los lenguajes de programacin.
Como podemos observar en el Listado
1, el uso de la librera dbm es realmente
simple. Se comporta como una lista,
con todas sus operaciones. El lector se
habr preguntado al ver el cdigo:
Dnde est el truco? si dbm represen-

Listado 1: Ejemplo de uso de DBM


01 >>> import dbm
02 >>> datos = dbm.open(visitantes,c) # crea el fichero
03 >>> datos[Juan Jose] = vendra
el martes
04 >>> datos[Juan Jose]
05 vendra el martes

06 >>> datos.close()
07 >>>
08 >>> datos = dbm.open(visitantes)
09 >>> datos[Juan Jose]
10 vendra el martes
11 >>> datos.keys()

12 [Juan Jose]
13 >>> for llave in datos.keys():
14 ... print [+llave+] -> +
datos[llave]
15 ...
16 [Juan Jos] -> vendra el martes
17 >>> datos.close()

15

29
30 def __len__(self):
31 return len(self.entradas())
32
33 def __setitem__ (self, clave,
valor):
34 self.bd[clave] = valor
35
36 def __getitem__(self,clave):
37 return self.bd[clave]
38
39 def __delitem__(self,clave):
40 del self.bd[clave]

Listado 2: almacen.py
01 #!/usr/local/bin/python
02
03 #!/usr/local/bin/python
04
05 import dbm
06 class Almacen:
07 def __init__ (self,nombre):
08 self.bd = dbm.open(nombre,c)
09
10 def busca_palabra (self, palabra):
11 claves = self.entradas()
12 encontradas = []
13
14 for clave in claves:

60

PYTHON

contenido =
self.contenido(clave)
16 if palabra in contenido:
17 encontradas.push(clave)
18
19 return encontradas
20
21 def entradas (self):
22 a = self.bd.keys()
23 if not a:
24 a = []
25 return a
26
27 def cierra (self):
28 self.bd.close()

W W W. L I N U X - M A G A Z I N E . E S

Curses

Figura 3: Insercin de una nueva entrada en


la bitcora.

ta una base de datos por qu puede


hacer uso de la sintaxis []?.
La respuesta es que en Python la sintaxis [] es lo que en ingls se llama
syntactic sugar. Por traducirlo de alguna
manera, viene a decir que es una manera
de hacer agradable visualmente (y a
nuestro pobres dedos) la llamada a ciertas
funciones del lenguaje.
Podemos incorporar [] a uno de
nuestro objetos y hacer que se comporte
como una lista? La respuesta es: S! y no
tiene nada de complicado.
Python reserva unas serie de mtodos
debido a su uso especial, entre ellos
estn:
def __len__(self)
def __setitem__(self, clave, valor)
def __getitem__(self, clave)
def __delitem__(self, clave)
Estos cuatro mtodos los enmascara
python posteriormente de la manera mos-

trada en la Tabla 1. Por tanto podemos


enmascarar las acciones de un objeto de
manera que se use como si fuese un diccionario. Y precisamente eso es lo que
hacemos con nuestro objeto Almacen que
encubre un diccionario, aadiendo nuevas acciones. El lector puede comprobar
el cdigo en el Listado 2 (disponible en
[1]).

Curses
Curses son unas libreras de bajo nivel.
Las abstracciones que crean son muy
bsicas: preparar consola, crear ventanas (nada que ver con las grficas),
escribir en esas ventanas, recoger caracteres y poco ms.
Debido a ello son bastante complicadas
de manejar. Hacer cosas vistosas suele llevar mucho cdigo. Por ello nos vamos a
centrar en un interfaz sencillo. Nuestro
programa ser modal, tendr un modo de
navegacin y uno de edicin, al igual
que el editor Vi. Precisamente Vi fue
uno de sus primeros usuarios.

Diseo Principal
Comenzaremos por inicializar curses. Por
desgracia, esto tambin nos hace perder
el control de nuestra consola Python,
puesto que anula su funcionamiento. Por
ello se pide al lector que ejecute todas las
acciones relacionadas con curses desde

LIBRERAS

Figura 4: Vista de la bitcora con varias


entradas.

un programa Python ejecutable (recuerda


hacer el chmod +x <programa>).
Podemos ver un programa que inicializa
la consola con curses en el Listado 3.
Posteriormente escribimos un Hola
mundo y refrescamos la pantalla, podemos ver el resultado en la Figura 1. Esta
parte es vital, si no refrescamos la pantalla curses no mostrar nada. En el Listado
3 stdscr representa toda la pantalla. Es
posible crear subventanas y hacer actualizaciones selectivas como podremos comprobar en el cdigo del programa.
Una vez realizadas las operaciones,
pasamos a dejar la pantalla en una
configuracin correcta, accin que realizan las cuatro ltimas llamadas a funciones.
El objeto diario crear a su vez un objeto GUI, que gestiona el interfaz, y el objeto Almacen que se encarga de gestionar la
base de datos.

Listado 3: Hola mundo con curses


01#!/usr/local/bin/python
02 # -*- coding: ISO8859-1 -*03
04 import curses
05
06 # Inicializamos la pantalla
07 stdscr=curses.initscr()

08 curses.noecho()
09 curses.cbreak()
10 stdscr.keypad(1)
11
12 # Escribimos algo
13 stdscr.addstr(Hola mundo,0)
14 stdscr.refresh()

15
16 # Limpiamos la pantalla
17 stdscr.keypad(0)
18 curses.echo()
19 curses.nocbreak()
20 curses.endwin()

08 uly, ulx = 15, 20


09 stdscr.addstr(uly-2, ulx, Use
Ctrl-G to end editing.)
10 win = curses.newwin(nlines,
ncols, uly, ulx)
11 rectangle(stdscr, uly-1, ulx-1,
uly + nlines, ulx + ncols)

12 stdscr.refresh()
13 return Textbox(win).edit()
14
15 str =
curses.wrapper(test_editbox)
16 print Contents of text box:,
repr(str)

Listado 4: Ejemplo de uso de Textbox


01#!/usr/local/bin/python
02 # -*- coding: ISO8859-1 -*03
04 import curses
05 import curses.textpad
06
07 ncols, nlines = 9, 4

Listado 5: Mtodo ejecuta_comando(self,ch)


01def ejecuta_commando(self, ch):
02 Procesa las teclas recibidas
03 if curses.ascii.isprint(ch):
04 for comando in self.comandos:
05
if comando[0] == chr(ch):
06
(getattr(self,comando[1]))()

W W W. L I N U X - M A G A Z I N E . E S

07
08
09

break
else:
if ch in (curses.ascii.DLE,
curses.KEY_UP):
10
self.incr_pos_fechas()
11
self.redibuja()

12

elif ch in (curses.ascii.SO,
curses.KEY_DOWN):
13
self.decr_pos_fechas()
14
self.redibuja()
15 self.refresca()
16 return 1

PYTHON

61

LIBRERAS

Curses

Listado 6: cuaderno.py
001 #!/usr/local/bin/python
002 # -*- coding: ISO8859-1 -*003
004 import curses
005 import curses.ascii
006 import curses.textpad
007 import time
008 import os.path
009 import string
010 import sys
011 import almacen
012
013 class GUI:
014 Interfaz con el usuario.
015
016 def __init__(self,datos):
017
018 self.registra_comandos()
019
020 self.datos = datos
021 self.pos_fechas =
len(self.datos) - 1
022
023 self.genera_ventanas()
024
025 self.banner(-- [n] nueva | [e]
editar | [q] salir --)
026
027 self.dibuja_fechas()
028
029 self.refresca()
030
031 def genera_ventanas(self):
032 Genera las ventanas iniciales
033 self.scr_fechas = stdscr.
subwin(23, 80, 0, 0)
034 self.scr_info = stdscr.
subwin(1,80,23,0)
035
036
037 def registra_comandos(self):
038 Almacena la letra y el comando
asociado
039 self.comandos = [[d,borrar],
040
[e,editar],
041
[n,nueva_entrada],
042
[q,salir],
043
[s,estado]]
044
045 def ejecuta_comando(self, ch):
046 Procesa las teclas recibidas
047 if curses.ascii.isprint(ch):
048 for comando in self.comandos:
049 if comando[0] == chr(ch):
050
(getattr(self,comando[1]))()
051
break
052 else:
053 if ch in (curses.ascii.DLE,
curses.KEY_UP):
054 self.incr_pos_fechas()
055 self.redibuja()
056 elif ch in (curses.ascii.SO,
curses.KEY_DOWN):

62

PYTHON

057
058
059
060
061
062
063
064

self.decr_pos_fechas()
self.redibuja()
self.refresca()
return 1
def fecha(self):
return time.strftime(%Y-%m-%d
%H:%M)

065
066 def long_fecha(self):
067 caja_texto = 2 # el | izquierdo
y el | derecho
068 return len(self.fecha()) +
caja_texto
069
070 def long_linea_texto(self):
071 return (80 - self.long_fecha())
072
073 def banner(self, texto):
074 Muestra el texto en la zona de
banner
075 self.scr_info.clear()
076 self.scr_info.addstr(texto,
curses.A_BOLD)
077 self.scr_info.refresh()
078
079 def dibuja_fechas(self):
080 Genera el listado de fechas de
la izquierda
081
082 self.scr_fechas.clear()
083 pos_x = 3
084
085 # 8 elementos por arriba y abajo
086 min = self.pos_fechas - 8
087 max = self.pos_fechas + 8
088
089 if max > len(self.datos):
090 max = len(self.datos)
091 min = min + (max len(self.datos))
092 if min < 0:
093 max = max + ( -min)
094 min = 0
095
096 if len(self.datos) > 0:
097 # Marcamos con negrita la fecha
sobre la que est el curso
098 # para ello iteramos escribiendo las fechas y cuando la
099 # encontramos le pasamos el
atributo de negrita
100 fechas = self.listado_fechas()
101 for fecha in fechas[min:max]:
102
103 fecha_temp = [+fecha+]
104
105 if fechas[self.pos_fechas] ==
fecha:
106
# Hemos encontrado nuestra
fecha!!!
107
self.scr_fechas.addstr
(pos_x,1,fecha_temp ,
curses.A_BOLD | curses.
A_UNDERLINE)

108
109
110

else:
self.scr_fechas.addstr
(pos_x,1,fecha_temp, 0)

111
112

self.scr_fechas.addstr
(pos_x,len(fecha_temp),
self.datos[fecha],0)
113 pos_x = pos_x + 1
114
115 self.refresca()
116
117 def editar(self):
118 Muestra un cuadro para
introducir el texto y lo guarda
119 if len(self.datos) == 0:
120 return
121 else:
122 self.banner( EDITANDO ! -[control+g] guardar | [q] salir
--)
123 fechas = self.listado_fechas()
124 texto = self.datos
[fechas[self.pos_fechas]]
125 self.refresca()
126
127 # Capturamos el nuevo texto.
128
129 ncols, nlineas = 25, 4
130 uly, ulx = 15, 20
131 stdscr.addstr(uly-2, ulx, Usa
Ctrl-G para guardar.)
132 win = curses.newwin(nlineas,
ncols, uly, ulx)
133 curses.textpad.rectangle
(stdscr, uly-1, ulx-1, uly +
nlineas, ulx + ncols)
134 stdscr.refresh()
135 nuevo_texto=
curses.textpad.Textbox(win).
edit()
136 stdscr.refresh()
137
138 nuevo_texto =
nuevo_texto.replace(\n,)
139 # Guardamos el nuevo texto
140 self.datos[fechas
[self.pos_fechas]] =
nuevo_texto
141
142 self.banner(-- [n] nueva | [e]
editar | [q] salir --)
143
144 def borrar(self):
145 Elimina la entrada seleccionada
146 if len(self.datos) > 0:
147 fechas = self.listado_fechas()
148 del self.datos
[fechas[self.pos_fechas]]
149 self.actualiza_pos_fechas()
150 self.redibuja()
151 self.refresca()
152
153 def redibuja(self):
154 Redibuja la pantalla
155 self.dibuja_fechas()

W W W. L I N U X - M A G A Z I N E . E S

Curses

El objeto Almacen es pasado a GUI


como parmetro en su creacin. Y la
misin de GUI no es otra que al de responder a los eventos que el usuario enve
mediante un bucle infinito.

Dos ventanas
Nuestro programa va a disponer de dos
ventanas. La mayor har las veces de
tabln donde podemos ver las anotaciones realizadas por el momento.
Podremos desplazarnos arriba y abajo por
l. Para indicar qu fecha es la que tenemos seleccionada la distinguiremos iluminndola en negrita y subrayndola.
La segunda venta har las veces de
barra de ayuda y estado. Cuando cambiemos el estado, por ejemplo al editar, se
reflejar ah. Es el mismo modo de trabajo
del que hace gala VIM.
Las ventanas deben partir la pantalla de
manera que no se solapen. La pantalla de

un terminal tiene 80 columnas de ancho y


25 filas de alto. Dejaremos una fila abajo,
que ser la que usemos para mostrar
informacin. La 24 filas restantes se
encargarn de mostrar las entradas almacenadas.

Desplazamiento por las entradas


La ventana de datos nos permitir desplazarnos arriba y abajo por las entradas. Cmo podemos conseguir recrear
este movimiento? La solucin es capturando las teclas de los cursores arriba
y abajo. Cuando una de ellas se pulse
incrementaremos o decrementaremos
una variable que establece la posicin
la entrada seleccionada en cada
momento y volveremos a dibujar, o
escribir, la pantalla de datos. Pero no lo
haremos de cualquier forma.
Queremos que el efecto sea vistoso, as
que siempre intentaremos mostrar un

LIBRERAS

nmero fijo de entradas encima y debajo


de la nuestra. Como tenemos la posicin
de la entrada seleccionada, o resaltada,
con un sencillo clculo podemos seleccionar que entradas mostraremos.
Las listas en Python tienen una funcionalidad que nos ser muy til. Usando la
sintaxis lista[comienzo:fin] podemos
extraer los elementos entre comienzo y
fin formando una nueva lista.
Simplemente tenemos que seleccionar
aquellos que estn a una distancia fija
del seleccionado y usarlos como comienzo y fin.
Podemos ver el cdigo que realiza esta
accin en el mtodo dibuja_fechas(self)
del GUI en el Listado 6.

Textbox
Python provee de una herramienta muy
til para la edicin de textos dentro de
curses. Desgraciadamente, a pesar de su

Listado 6: cuaderno.py (cont.)


156
157
158
159
160
161
162

self.refresca()
def refresca(self):
self.scr_fechas.refresh()

def listado_fechas(self):
Devuelve el listado de fechas
ordenado
163 fechas = self.datos.entradas()
164 fechas.sort(reverse=-1)
165 return fechas
166
167 def decr_pos_fechas(self):
168 Mueve la fecha seleccionada
hacia arriba
169 if self.pos_fechas >=
(len(self.datos) - 1):
170 self.pos_fechas =
len(self.datos) - 1
171 else:
172 self.pos_fechas += 1
173
174 def incr_pos_fechas(self):
175 Mueve la fecha seleccionada
hacia abajo
176 if self.pos_fechas <= 0:
177 self.pos_fechas = 0
178 else:
179 self.pos_fechas -= 1
180
181 def actualiza_pos_fechas
(self,fecha=):
182 Realiza los cambios oportunos
cuando se elimina una fecha
183 if fecha == :
184 self.decr_pos_fechas()
185 else:
186 fechas = self.listado_fechas()
187 cont = 0

W W W. L I N U X - M A G A Z I N E . E S

188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205

resultado = 0
for f in fechas:
if f == fecha:
resultado = cont
break
cont += 1
self.pos_fechas = resultado
self.redibuja()
self.refresca()

def nueva_entrada(self):
Introduce una nueva fecha
fechas = self.listado_fechas()
fecha = self.fecha()
if not fecha in fechas:
self.datos[fecha] = Texto
vacio
206 self.actualiza_pos_fechas
207 self.redibuja()
208 self.refresca()
209 self.editar()
210
211 def salir(self):
212 cierra_curses(stdscr)
213 sys.exit(0)
214
215 def estado(self):
216 Muestra informacin general
217 cadena =
218 fechas = self.listado_fechas()
219 cont = 0
220 for fecha in fechas:
221 cadena +=
[+str(cont)+>+fecha+]
222 cont += 1
223

224 cadena =
[P:+str(self.pos_fechas)+]
+--[L:+str(len(self.datos))+
] + cadena
225 self.banner(cadena)
226
227 class Diario:
228 def __init__(self):
229 self.datos = almacen.Almacen(diario)
230 self.gui = GUI(self.datos)
231 self.bucle_infinito()
232
233 def bucle_infinito(self):
234 while 1:
235 c = stdscr.getch()
236 n = self.gui.ejecuta_comando
(c)
237 if not n:
238 break
239
240 def arranca_curses(stdscr):
241 curses.noecho()
242 curses.cbreak()
243 stdscr.keypad(1)
244
245 def cierra_curses(stdscr):
246 stdscr.keypad(0)
247 curses.echo()
248 curses.nocbreak()
249 curses.endwin()
250
251 if __name__==__main__:
252 stdscr=curses.initscr()
253 arranca_curses(stdscr)
254 diario = Diario()
255 cierra_curses(stdscr)

PYTHON

63

LIBRERAS

Curses

potencia posee algunos inconvenientes de


los que hablaremos ms tarde.
Esta herramienta es el objeto Textbox
que se encuentra en la librera curses.textpad. Textbox nos permite editar un texto
dentro de una ventana y poder utilizar
muchas de las combinaciones de teclas
que soporta EMACS. As por ejemplo con
control+e iremos al final de la linea
que estemos editando y con control+d
borraremos el carcter sobre el que nos
encontremos.
Utilizaremos un Textbox para recoger el
texto que el usuario quiera introducir.
Desgraciadamente posee una limitacin
debido su diseo: Si cuando estamos editando el texto, pulsamos repetidas veces
el cursor izquierdo desplazndonos hasta
dar con el borde de la ventana, el programa fallar.
El soporte de curses de Python se basa
en las libreras originales escritas en C, y
como ya hemos dicho son de muy bajo
nivel. La implementacin de Textbox es
realmente bsica y no controla todas las
circunstancias. An as servir.
Un ejemplo de utilizacin de Textbox
aparece en su propio cdigo fuente, ver
Listado 4 y Figura 2. Este cdigo genera
un rectngulo con bordes (usando la funcin rectangle de curses.textpad) y nos
solicita que escribamos algo en el mismo.
Para acabar debemos pulsar control+g,
mostrndonos lo escrito ms abajo. Si lo
probamos comprobaremos que no podemos salir del rectngulo al editar.
En la Figura 3 vemos como hemos integrado el Textbox en nuestro programa.
Hemos aumentado el nmero de columnas para permitir introducir mensajes
ms largos.

El Gestor de Comandos
Existen muchas maneras de hacer un gestor de comandos. La ms tpica consiste
en hacer una sentencia switch o gran can-

tidad de ifs anidados, cada uno de los


cuales responde ante una tecla o combinacin distinta. El cdigo generado llegar
a convertirse en ilegible en cuanto el
nmero de comandos sobrepasa los diez.
Hay una manera mucho ms elegante
de atacar este problema, pero no es tan
fcil hacer uso de ella en todos los lenguajes. Afortunadamente Python nos permite
una implementacin muy sencilla. La
idea es la siguiente: Cada comando estar
asociado a una serie de acciones a realizar. Englobaremos las acciones vinculadas con cada comando a un mtodo de
nuestro objeto GUI. Hasta aqu todo es
bastante normal. Ahora viene la magia.
Python nos permite invocar mtodos de
objetos usando su nombre. Si declaramos
el objeto persona:
>>> class Persona:
... def habla(self):
...
print "hola mundo"
...
>>>

Podemos invocar el mtodo habla usando


una cadena con su nombre mediante el
mtodo getattr(), que precisa de la instancia del objeto y el mtodo a invocar.
Devuelve, por as decirlo, una referencia
al mtodo en cuestin que funciona de la
misma manera. Como dicen por ah, una
imagen vale ms que mil palabras:
>>> pepe = Persona()
>>> pepe.habla()
hola mundo
>>> (getattr(pepe, "habla"))()
hola mundo
>>>

Lo que haremos ser crear una lista de listas, cada una de las cuales contendr dos
elementos. El primero ser un carcter y
el segundo el nombre del mtodo a invo-

Tabla 1: Algunos mtodos especiales de Python


Mtodo

Descripcin

__len__(self)

devuelve la longitud de nuestro objeto. Se invoca cuando se ejecuta


len(miObjeto)

__setitem(self, clave,valor)

se corresponde con la asignacin


miObjeto[Algo] = otra cosa

en

un

es el equivalente miObjeto[Algo] y devuelve la informacin almacenada en Algo.

__delitem(self, clave)

es del miObjeto[Algo] y se corresponde con la eliminacin de esa


entrada.

PYTHON

Uso del Programa


El uso del programa se ha hecho lo ms
simple posible, el aspecto del mismo se
puede ver en la Figura 4. Cuando se pulsa
n se crea una nueva entrada con la
fecha y la hora, si existe ya una entrada
con la fecha y la hora no se hace nada.
Con d se elimina una entrada y con e
se edita. Cuando se est introduciendo un
texto, al pulsar control+g se guarda.
Para salir se pulsa q y con los cursores
arriba y abajo nos desplazamos por la
lista.
Al fichero de almacenado se le ha dado
el nombre diario.db. Si no existe se
crea, y si existe se emplea el existente.

Conclusin
Aunque el uso de Curses puede resultar
engorroso, Python nos provee de una
librera que las manipula dentro su instalacin base. Una vez realizado el programa sabremos que cualquiera que instale
Python podr hacer uso de l.
Siempre es posible realizar una serie de
objetos que realicen tareas de ms alto
nivel. Existen libreras que nos proporcionan barras de mens y widgets ms avanzados. An as, siempre es bueno estar lo
ms cerca posible del estndar.
La prxima vez que tengas que hacer
un interfaz en modo texto puede que sea
una buena idea darle una oportunidad a
curses.

diccionario:

__getitem(self, clave)

64

car. De esta manera, nuestro gestor de


comandos se reduce a un cdigo que recibe un carcter, lo compara con el primero
elemento de cada entrada en su lista de
comandos y si encuentra una coincidencia ejecuta el comando asociado. Ver
Listado 5.
Como podemos ver en el cdigo, se
comprueba si el carcter recibido es
imprimible y posteriormente se busca en
la lista de comandos. En caso de coincidencia se ejecuta usando como instancia
self. De esta manera, es posible manipular
el funcionamiento ante qu caracteres responde el programa sin tener que modificar
el cdigo fuente. Esto nos da mucha flexibilidad y es menos propenso a errores.

Recursos
[1] Descargas de los listados de este artculo: http://www.linux-magazine.es
/Magazine/Downloads/Especiales/06_
Python

W W W. L I N U X - M A G A Z I N E . E S

3D con VTK

Visualizacin 3D con VTK (Visualization Toolkit)

Grficas 3D
Hoy por hoy, la representacin grfica 3D y su visualizacin forman
parte de nuestra vida cotidiana; basta fijarse en el mundo del
entretenimiento, en la industria del juego y en el soporte de hardware y software para tales fines. Quin en su ordenador personal
no ha instalado un juego o visto una pelcula renderizada en 3D?
Por Ana M. Ferreiro Ferreiro y Jos A. Garca Rodrguez

La representacin grfica en 3D ofrece


la posibilidad de crear mundos virtuales
en un ordenador, lo cual, unido a la
visualizacin permite al usuario explorar
y entender, rpidamente, sistemas complicados. Esto es posible gracias al
avance de lenguajes orientados a objetos, que ofrecen la posibilidad de crear
software de mejor calidad y ms fcil de
mantener.
Entre las diferentes herramientas de
visualizacin, representacin 3D y
procesamiento de imgenes, cabe
destacar VTK (Visualization Toolkit),
cdigo abierto cuyo ncleo est implementado en C++ y que soporta
envolturas (wrappers) para TCL,
Python y Java, permitiendo el desarrollo
de aplicaciones complejas de un modo
eficiente y mediante scripts sencillos. Por
todo ello, VTK se emplea en la visualizacin mdica, la visualizacin indus-

trial, reconstruccin de superficies a partir de digitalizacin lser o nubes de


puntos desorganizados, etc.
En lo que sigue veremos los conceptos
bsicos en los que se basa VTK para
poder generar una escena y, mediante
una serie de ejemplos desarrollados en
Python, llegaremos a crear nuestras
propias escenas de visualizacin.

Instalacin
Para poder realizar todas las pruebas que
se van sugiriendo y las que se os ocurran, es necesario tener instalado Python
y VTK con soporte para Python. Adems,
la tarjeta grfica de nuestro ordenador
debe tener OpenGL funcionando.
La instalacin de las libreras VTK
(que no suelen estar instaladas de manera predeterminada) es muy sencilla.
Todas las distros mayoritarias cuentan
con los paquetes necesarios en sus
repositorios.
En Debian o Ubuntu, por ejemplo,
bastar con ejecutar

LIBRERAS

realizando. Por un momento, imaginad


que estis en la butaca del cine, viendo
una pelcula de animacin, como por
ejemplo La Edad de Hielo. Si nos centramos en una nica escena y la describimos, vemos personajes animados
(actores), luces de diferentes tonalidades, cmaras que modifican el punto
de vista, propiedades de los personajes
(color, forma, etc.). Aunque no lo creis,
todos estos conceptos son la base de la
visualizacin grfica. Veamos dicha
estructura.
El toolkit de visualizacin VTK est
diseado a partir de dos modelos claramente diferenciables: el modelo grfico y
el modelo de visualizacin.
Modelo grfico. El modelo grfico
captura las principales caractersticas
de un sistema grfico 3D, de un modo
fcil de entender y usar (ver Figura
1). La abstraccin se basa en la industria del cine. Los objetos bsicos que
constituyen este modelo son: vtkRenderer, vtkRenderWindow, vtkLight,
vtkCamera, vtkProp, vtkProperty,
vtkMapper, vtkTransform. En la Tabla
1 se describen cada uno de estos objetos.
Modelo de visualizacin. El papel del
modelo grfico es transformar datos
grficos en imgenes, mientras que el
del modelo de visualizacin transforma informacin en datos grficos;
esto significa que el modelo de visualizacin es el responsable de construir
la representacin geomtrica que se
renderiza mediante el modelo grfico.
VTK se basa en la aproximacin de los
datos para transformar la informacin
en datos grficos. Hay dos tipos bsicos de objetos, descritos en la Tabla 2,
involucrados en dicha aproximacin:
vtkDataObject y vtkProcessObject.

apt-cache search vtk

para ver las que necesitamos.

Modelos de Objetos VTK

Figura 1: Estructura del modelo grfico.

W W W. L I N U X - M A G A Z I N E . E S

Para los inexpertos en el mundo de la


visualizacin, vamos a explicar de un
modo sencillo la estructura de VTK, ya
que esto permite que comprendamos
mejor cada uno de los pasos que iremos

Figura 2: Tipos de datos: a) datos poligonales, b) puntos estructurados c) malla no


estructurada d) malla estructurada.

PYTHON

65

LIBRERAS

3D con VTK

Los diferentes tipos de datos que pueden


constituir un objetos son, entre otros,
puntos, rectas, polgonos, puntos estructurados, mallas estructuradas y no
estructuradas, etc. (ver Figura 2).

Mi Primera Escena
Ya estamos preparados para construir
nuestra primera escena. Situaros en el
papel de director de cine. En los siguientes ejemplos veremos el modo de
emplear las clases que acabamos de
describir. Para ello, tal como se menciona al comienzo, instanciaremos VTK
desde Python.
Con cualquier editor de textos,
creamos el fichero cone.py. Lo primero
es importar desde Python el paquete
VTK; esto es tan sencillo como escribir la
siguiente lnea:
import vtk

Ahora que ya podemos instanciar


cualquier objeto de VTK, sin ms que
escribir vtk.nombre_clase, necesitamos
crear nuestra ventana de renderizado
vtk.vtkRenderWindow, a la que llamaremos renWin y a la que asociamos un
rea de renderizado vtk.vtkRenderer (que
denominamos ren), mediante el mtodo
AddRenderer(). Escribamos las siguientes lneas de cdigo:

ren=vtk.vtkRenderer()
renWin=vtk.vtkRenderWindow()
renWin.AddRenderer(ren)
iren=vtk.U
vtkRenderWindowInteractor()
iren.SetRenderWindow(renWin)

Para poder manipular la cmara mediante el ratn se ha instanciado el objeto


vtkRenderWindowInteractor (denominado en el cdigo como iren). Ntese
que la ventana de renderizado renWin se
asocia al objeto de interaccin iren mediante el mtodo SetRenderWindow. En
este momento no se aprecia la utilidad
del mismo, paciencia ya comprenderis su importancia cuando tengamos
un actor en nuestra escena.
Guardamos el fichero y en la lnea de
comandos ejecutamos el programa tecleando python cone.py No ocurre nada!
Esto es porque debemos inicializar la
interaccin del usuario e indicar que la
ventana de renderizado permanezca visible hasta que el usuario finalice la ejecucin de la misma cerrndola. Para ello
basta escribir
iren.Initialize()
iren.Start()

sus botones de minimizar, maximizar y


cerrar; y que slo se cierra cuando el
usuario lo estima oportuno (Figura 3).
Esta ventana va a ser el contenedor de
nuestra pequea escena. Ntese que las
dos lneas de cdigo que acabamos de
escribir deben de estar al final del
fichero. Las dems lneas que escribamos
a partir de este momento debemos situarlas justo antes.
Para crear nuestro primer actor no nos
vamos a complicar demasiado, porque
ya queremos ver algo. VTK contiene una
serie de clases que nos permiten crear
objetos tridimensionales sencillos, como
son: esfera (vtkSphereSource), cono (vtkConeSource),
cilindro
(vtkCilinderSource), etc. Para nuestro ejemplo hemos
escogido un cono, sin embargo, puedes
optar por cualquiera de los otros objetos.
El siguiente cdigo nos permite crear
nuestro primer actor,
cone=vtk.vtkConeSource()
coneMapper=vtk.U
vktPolyDataMapper()
coneMapper.SetInput(cone.U
GetOutput())
coneActor=vtk.vtkActor()
coneActor.SetMapper(coneMapper)

Si ejecutamos nuevamente el programa,


se abre una ventana de color negro con

Mediante el objeto vtk.vtkConeSource


creamos una representacin poligonal

18 esferaActor.SetMapper
(esferaMapper)
19 esferaActor.GetProperty().SetColor(0.7,0.0,0.25)
20 esferaActor.GetProperty().
SetOpacity(0.75)
21 esferaActor.GetProperty().
SetLineWidth(1)
22
23 # Creamos: Renderer, Render
Window, RenderWindowInteractor
24 ren = vtk.vtkRenderer()
25 renWin = vtk.vtkRenderWindow()
26 renWin.AddRenderer(ren)
27 iren = vtk.vtkRenderWindow
Interactor()
28 iren.SetRenderWindow(renWin)
29
30 # Aadimos el actor en el rea
de renderizado (Renderer)
31 ren.AddActor(coneActor)
32 ren.AddActor(esferaActor)
33

34 #Fijamos el color de fondo, el


tamao y hacemos zoom sobre
35 #el area de Renderizado
36 ren.SetBackground(1, 1, 1)
37 renWin.SetSize(450, 425)
38 camera=ren.GetActiveCamera()
39 ##camera.Zoom(1.5)
40
41 coneActor.RotateX(30)
42 coneActor.RotateY(45)
43 conepro=coneActor.GetProperty()
44 conepro.SetColor(0,0.6,1)
45 ##conepro.SetOpacity(0.5)
46 conepro.SetLineWidth(2)
47 ren.ResetCamera()
48 ##camera=ren.GetActiveCamera()
49 camera.Zoom(1.5)
50
51 cone.SetResolution(40)
52
53 iren.Initialize()
54 renWin.Render()
55 iren.Start():

Listado 1: cono_esfera.py
01 import vtk
02
03 # Generamos la estructura para
ver un cono
04 cone = vtk.vtkConeSource()
05 coneMapper = vtk.vtk
PolyDataMapper()
06 coneMapper.SetInput(cone.
GetOutput())
07 coneActor = vtk.vtkActor()
08 coneActor.SetMapper(coneMapper)
09
10 # C r e a r f u e n t e d e e s f e r a ,
mapeador y actor
11 esfera = vtk.vtkSphereSource()
12 esferaMapper = vtk.
vtkPolyDataMapper()
13 esfera.SetPhiResolution(10)
14 esfera.SetThetaResolution(20)
15 esfera.SetCenter(0.3,0.0,0.0)
16 esferaMapper.SetInput(esfera.
GetOutput())
17 esferaActor = vtk.vtkActor()

66

PYTHON

W W W. L I N U X - M A G A Z I N E . E S

3D con VTK

LIBRERAS

Tabla 1: Modelo Grfico


Objeto

Descripcin

vtkRenderer

crea un rea de renderizado que coordina: luces, cmaras y actores.

vtkRenderWindow

clase que representa el objeto dentro del cual se colocan una o ms reas
de renderizado (vtkRenderer).

vtkLight

objeto que permite manipular las luces de la escena. Cuando se crea una
escena, por defecto se incluyen luces.

vtkCamera

objeto que controla como una geometra 3D es proyectada dentro de la


imagen 2D durante el proceso de renderizado. La cmara tiene diferentes
mtodos que permiten definir el punto de vista, el foco y la orientacin.

Figura 3: Pasos que en general hay que


seguir para crear un actor.

vtkProp

objeto que representa los diferentes elementos (actores) que se sitan dentro de la escena. Caben destacar las siguientes subclases: vtkActor, vtkVolume, vtkActor2D.

de un cono, que hemos llamado cone.


La salida del cono (cone.GetOutput())
es un conjunto que se asocia al mapper
(coneMapper)
(vtk.vtkPolyDataMapper) va el mtodo SetInput().
Creamos el actor (objeto que se va renderizar) al que se le asocia la representacin geomtrica que aporta
coneMapper. Ntese que los pasos aqu
indicados son los que, en general, necesitamos seguir para poder construir un
actor (Figura 4).
Cuando creamos un actor, no se
incluye por defecto en la escena. Es
necesario aadirlo al Renderer mediante
AddActor, y posteriormente renderizar la
escena. Esto se logra escribiendo,

vtkProperty

representa los atributos de renderizado de un actor, incluyento color, iluminacin, mapeado de la estructura, estilo de dibujo y estilo de la sombra.

vtkMapper

representa la definicin de la geometra de un actor y mapea los objetos


mediante una tabla de colores (vtkLookupTable). El mapper proporciona
la frontera entre el modelo de visualizin y el modelo grfico.

vtkTransform

objeto consistente en una matriz de transformacin 4x4 y mtodos para


modificar dicha matriz. Especifica la posicin y orientacin de actores,
cmaras y luces

ren.AddActor(conoActor)
renWin.Render()

Si volvemos a ejecutar, visualizamos un


cono de color gris (color que se muestra
por defecto) dentro de nuestra ventana
(Figura 5). Adems, es en este instante
cuando se aprecia la interaccin con el
ratn; con el botn izquierdo puedes
rotar la cmara, el botn central permite
trasladarla, y con el botn derecho nos
acercamos o alejamos del objeto.
Adems, habrs observado que en la
escena, por defecto se incluye una luz
para poder visualizar los objetos iluminados.

Figura 4: Ventana de renderizado por defecto.

W W W. L I N U X - M A G A Z I N E . E S

Tabla2: Modelo de Visualizacin


Objeto

Descripcin

vtkDataObject

clase genrica que permite representar diferentes tipos de datos. Los objetos de datos consisten en estructuras geomtricas y topolgicas (puntos y
celdas), y tambin en atributos asociados, tales como escalares o vectores.

vtkProcessObject

objeto que hace referencia a filtros, que actan sobre los actores modificndolos.

Prueba
a
comentar
la
lnea
renWin.Render(). Qu ocurre? Como
habrs podido observar el cono ya no
aparece, esto es porque cada vez que
aadimos un actor es necesario renderizar la escena, ya que de lo contrario
no se realiza un refresco de la misma y
es como si no hubisemos aadido un
nuevo actor.

Habrs observado que la ventana de


renderizado se abre con un tamao predeterminado. Para fijar el tamao de
dicha ventana es necesario emplear el
mtodo SetSize, donde indicamos el alto
y el ancho en pixels,
renWin.SetSize(450,325)

Propiedades de Objetos

Si has seguido el tutorial hasta este


punto, habrs creado tu cono de color
gris. Pero probablemente no ests
demasiado satisfecho, porque todos tenemos el mismo cono gris y t
lo queras blanco y el fondo
azul, por ejemplo. A lo largo
de este apartado veremos
cmo modificar la ventana de
renderizado,
la
cmara,
propiedades del actor, etc. Al
final, podrs realizar todos
Figura 5: Cono dentro de
aquellos cambios que te
la escena.
apetezcan.

Figura 6: Comportamiento de los mtodos de


la cmara. a) Azimuth - flechas rojas; b)
Pitch - flechas azul celeste; c) Yaw - flechas
azul oscuro; d) Elevation - flechas verdes; e)
Roll - flecha amarilla. La esfera blanca representa el foco.

PYTHON

67

LIBRERAS

3D con VTK

Si lo que pretendemos es cambiar el


color de fondo de la escena (vtkRenderer) empleamos el mtodo SetBackground (RGB), donde le pasamos el color
deseado en formato RGB. Si queremos
un fondo azul bastara escribir
ren.SetBackground(0.0, 0.0, 1)

Como dijimos, el rea de renderizado


(vtkRenderer) coordina la cmara y las
luces. Mediante el mtodo GetActiveCamera() se accede a la cmara creada
en la escena, as podemos aplicarle todos
los mtodos del objeto vtkCamera para
poder modificar la visualizacin segn
queramos. Si lo que pretendemos es que
todos nuestros actores se vean en su
totalidad dentro del rea de renderizado,
es necesario llamar al mtodo ResetCamera(). En las siguientes lneas se recogen algunos de los mtodos relativos a la
cmara
ren.ResetCamera()
camera=ren.GetActiveCamera()
camera.Azimuth(60)
camera.Pitch(5)
camera.Yaw(5)
camera.Roll(50)
camera.Elevation(20)
camera.Zoom(1.5)

Los mtodos Azimuth, Pitch, Yaw,


Roll,Elevation se ocupan de rotar la
cmara o el punto de foco en diferentes
direcciones y, como argumento, se pasa
un ngulo de rotacin. Lo mejor es que
juegues un poco con la cmara y veas lo
que ocurre probando cada uno de estos
mtodos por separado. Por ejemplo, para
ver cmo afecta el mtodo Azimuth aplicado a la cmara, comenta las restantes
lineas de cdigo, por que si no estaras

mezclando distintos mtodos de


rotacin y uno as
no sabe realmente
lo que ocurre. Si
en
algn
momento el actor
desaparece de la
escena no te preocupes, lo que
est sucediendo
Figure 7: Vista de la superficie
Figura 9: Escena con dos actores
es que el ngulo
del cono.
que se intersecan.
de rotacin ha
colocado la cmara justo en un punto
La lnea conepro.SetResolution(40)
que evita que visualicemos el objeto
modifica la resolucin con la que se rendentro de la escena. En la Figura 6 se
deriza el cono. Este mtodo no es general
explica de un modo sencillo el modo en
para todos los actores, sino para ciertos
que actan cada uno de estos mtodos
objetos que VTK ya incluye, como son:
respecto del foco (representado por una
esfera (vtkSphereSource), cono (vtkConeesfera blanca).
Source), cilindro (vtkCilinderSource), etc.
Cualquier objeto puede rotarse,
A partir de este punto comentad las
escalarse, obtener su dimensiones, etc.,
lneas de cdigo correspondientes a los
utilizando las propiedades de un vtkActor
mtodos que actan sobre la cmara,
en particular(si queris ms informacin,
dejando nicamente la lnea camebasta consultar las ayuda de VTK sobre
ra.Zoom(1.5). As vamos viendo cada
vtkProp3D, que es la clase padre). Para
cosa por separado, despus ya tendris
rotar nuestro cono y escalarlo basta
tiempo de mezclar cdigo.
escribir
Ahora que sabemos modificar la
escena, debemos recordar que el cono
contina vindose en un color gris un
coneActor.RotateX(30)
poco apagado. Para acceder a las
coneActor.RotateY(45)
propiedades de cualquier actor vtkActor
coneActor.SetScale([1,3,2])
se emplea el mtodo GetProperty(), que
devuelve una instancia del objeto
Ahora ya sabis crear vuestra propia
vtkProp asociado a dicho actor. Las
escena, modificar sus propiedades,
siguientes lneas permiten modificar el
aadir un actor con las opciones que
color, la transparencia y grosor de las
queris y modificar la cmara. En caso
lneas:
de que quisierais aadir ms actores a
vuestra ventana de renderizado, basta
conepro=coneActor.GetProperty()
seguir el mismo procedimiento que
conepro.SetColor(1,0.2,0)
hemos empleado para crear nuestro
conepro.SetOpacity(0.5)
cono. En el Listado 1 se aade a la
conepro.SetLineWidth(3)
escena un cono y una esfera que se interconepro.SetResolution(40)
secan (Figura 9).

conepro.U
SetRepresentationToWireframe()

Figura 8: Vista de la malla del cono.

68

PYTHON

La ltima lnea de este cdigo se indica


que queremos ver la estructura bsica
que constituye el actor, es decir, el mallado. Por defecto, VTK tiene asociadas
teclas rpidas a la escena: si tecleas la
letra s se ven todos los objetos renderizados (ver Figura 7), mientras que si
tecleas la letra w, se visualiza slo la
malla (ver Figura 8). Ahora apreciars
mejor la diferencia entre mallado y
estructura renderizada, no hay nada
mejor que poder ver las cosas.

Recursos
[1] RPMs VTK de Mandrake:
ftp://ftp.rediris.es/sites3/carroll.cac.
psu.edu/mandrakelinux/official/10.1/
i586/media/contrib/
[2] Kitware. VTK:
http://www.kitware.org
[3] Enthought. Scientific python:
http://www.scipy.org
[4] MayaVi: http://mayavi.sourceforge.net
[5] Cdigo de este artculo: http://www.
linux-magazine.es/Magazine/Downloads/Especiales/06_Python

W W W. L I N U X - M A G A Z I N E . E S

PIL

LIBRERAS

Imgenes satlite en Python

Vigilantes
del Planeta
Quin no ha querido alguna vez sentirse como esos informticos de la NASA en su centro de control? Hoy
nos construiremos el nuestro y controlaremos el planeta y sus alrededores.
Por Jos Mara Ruz y Pedro Orantes

Cada vez que vemos el lanzamiento de


un cohete, todos quedamos asombrados
ante la explosin del despegue, la atenta
mirada de todos esos cientficos a los
paneles de control, y la monstruosa cifra
que nos dicen que se han gastado en el
proyecto.

Donde Van los Impuestos?


Es entonces cuando surge la pregunta y
eso a m en qu me repercute? Un da,
estando en el despacho de la Rama del
IEEE de Mlaga tuve una conversacin
en la que me contaron que la mayor
parte de los satlites emiten al mundo
las imgenes y los datos que recogen. Es
decir, si se posee el equipo necesario es

W W W. L I N U X - M A G A Z I N E . E S

posible recibir en tu propia casa imgenes fascinantes del universo, de Marte o


de la Tierra.
La temperatura del ocano, imgenes
meteorolgicas, imgenes del campo
magntico del sol o de las misiones a
Marte son enviadas constantemente a la
Tierra desde estos engendros espaciales.
Y el efecto es siempre el mismo, el espectador es deslumbrado por el presentador
de televisin con unas imgenes increbles mientras se escuchan acordes de
sintetizador.
Acaso no son esas imgenes de dominio pblico? Dnde puedo conseguirlas?
En el presente artculo utilizaremos
Python para crear un script CGI que nos

permita recoger y mantener actualizadas


las imgenes que queremos en una especie de collage o mural. Construiremos
nuestro propio centro de control espacial.

Recoger las Imgenes


Lo primero ser encontrar las imgenes y
reunirlas. Vamos a usar como ejemplo
cuatro imgenes de carcter cientfico.
Se actualizan a distintos intervalos, de
manera que podremos ver cmo evolucionan las eventos que se registran.
Puedes encontrar las URLs en Recursos
[1].
Debemos descargar las imgenes y
almacenarlas dentro de nuestro progra-

PYTHON

69

LIBRERAS

PIL

La librera httplib de Python establece


en un primer paso una conexin con el
servidor remoto mediante el mtodo
HTTPConnection
>>> c = httplib.HTTPConnectionU
("www.linux-magazine.es")
>>>

En la variable c almacenamos el objeto


que representa la conexin realizada y
podemos enviar peticiones.
Figura 1: La imagen original que vamos a
modificar con PIL.

ma; haremos uso de la librera httplib,


que es parte de la distribucin estndar
de Python. Esta librera nos permitir
hablar de t a t con un servidor web
remoto sin tener que preocuparnos de
los detalles de ms bajo nivel. Esta conversacin la realizaremos usando el protocolo HTTP. Este protocolo es bastante
simple, y de l solo necesitaremos una
parte mnima.
Cuando Tim Berners Lee realiz el
diseo original de la Web, quiso que el
protocolo para pedir los documentos
fuese lo ms simple posible. HTTP se
reduce a la recepcin y el envo de informacin al servidor, eso y slo eso. Se
compone de varios comandos, pero los
ms conocidos son GET, que podemos
traducir como tomar, y POST, que
podemos traducir en este contexto como
enviar o mandar. As que tomamos
documentos y enviamos informacin.
Una parte importante de HTTP es URL,
que nos sirve para darle nombre a esos
documentos. Todos estamos acostumbrados a tratar con urls, generalmente del
tipo
http://www.linux-magazine.es/
issue/08. La url se compone de: [protocolo]://[maquina]/[ruta]/[objeto]. Vamos la
ver ahora porqu es tan importante que
sepamos esto.

>>> c.request("GET","/issue/08")
>>>

Usamos el comando GET, con lo que


estamos solicitando un objeto. El segundo parmetro del mtodo es la ruta
hasta el objeto. As que la URL que estamos
solicitando
es
http://www.
linux-magazine.es/index.html. Es importante que la ruta comience con una barra
/, como si fuese la ruta de un fichero
de una mquina. Cmo sabemos si todo
ha ido bien?
>>> r = c.getresponse()
>>> print r.status,r.reason
200 OK
>>>

Con getresponse podemos conseguir un


objeto que representa los datos devueltos por la conexin. Este objeto tiene,
entre otros, los atributos status y reason
que nos indican el estado, un nmero
con un significado especial, y la explicacin del mismo. En este caso todo ha ido
bien, y por eso recibimos un OK. En
caso contrario, si no existiese la ruta que
pedimos habramos obtenido:
>>> r = c.getresponse()
>>> print r.status, r.reason
400 Bad Request
>>>

Figura 2: La imagen del pequeo demonio de


BSD rotado 45 con PIL.

Spamassasin, Hypermail,U
Encriptacin GPG, SDL,U
...

Cuando hayamos finalizado debemos


cerrar la conexin invocando el mtodo
close() del objeto que representa la conexin, en este caso sera:
>>> c.close()
>>>

Para obtener las imgenes vamos a hacer


exactamente lo mismo, abriremos una
conexin, pediremos la imagen, la almacenaremos en un diccionario y cerraremos la conexin.

Python Imaging Library


Nuestra idea original era realizar un
mural o collage con las imgenes recuperadas. Python no nos provee de una
librera de tratamiento grfico en su distribucin estndar. Eso no quiere decir
que no exista tal librera, ya que no slo
existe, sino que adems es muy potente
y til. Nos referimos a Python Imaging
Library (ver URL [2] en el Listado de
Recursos al final del artculo).
La librera PIL (Python Imaging
Library) nos va a permitir tratar imge-

Listado 1: Ejemplo Uso de PIL

Curiosidad
Poco tiempo despus de finalizar este
artculo apareci una noticia en Slashdot
(ver Recursos [4]) hablando de una llamarada solar de tal tamao que iba a
alterar las comunicaciones. Cuando se
dan este tipo de eventos en muchos centros de control de satlites, los ingenieros cruzan los dedos para que sus satlites no caigan ante la ola de viento solar
que se origina. El lector puede apreciar
la llamarada en la Figura 4.

70

PYTHON

Ahora ya tenemos la pgina, slo tenemos que leerla-- usando el mtodo


read() del objeto respuesta.
>>> print r.read()
<html>
<head>
<base href="http://www.U
linux-magazine.es/issue/08/" />
<title>Linux Magazine -U

01 >>>mural =
Image.new('RGB',(600,480))
02 >>> im =
Image.open("daemon.jpg")
03 >>> im.thumbnail((300,200),
Image.ANTIALIAS)
04
05
06
07

>>> mural.paste(im,(0,0))
>>> mural.paste(im,(300,0))
>>> mural.show()
>>>

W W W. L I N U X - M A G A Z I N E . E S

LIBRERAS

PIL

Figura 3: Creamos una imagen vaca con PIL


y despus colocamos otras imgenes en su
interior como mosaico.

nes en una gran cantidad de formatos.


Podremos convertirlas a otro formato,
rotarlas, escalarlas, mezclarlas, etc.
Aquel lector que haya tenido contacto
con programas de manipulacin grfica,
como por ejemplo GIMP (ver URL [3] en
el Listado Recursos), comprender la
potencia de una librera con estas funcionalidades.
Como no viene de serie con Python,
deberemos instalarla en nuestra distribucin o sistema operativo. Existen paquetes RPM y DEB de la misma.
Cmo se trabaja con PIL? Pues
mediante la manipulacin de objetos de
la clase Image. Esta clase es capaz de
albergar imgenes de casi cualquier formato, permitindonos manipularlas.
Vemos un ejemplo. En la Figura 1 podemos ver la imagen original del fichero
daemon.jpg en mi equipo. Vamos a rotarla 45 grados:
>>> import Image
>>> im = Image.openU
("daemon.jpg")
>>> img.rotate(45).show()
>>>

En la Figura 2 podemos ver el resultado.


Hemos usado el mtodo rotate(), al que
hemos pasado un ngulo de 45 grados, y
en el resultado hemos invocado el mtodo show() que mostrar el resultado
mediante el programa xv (para cerrar xv
slo tenemos que pulsar q).
Nosotros no buscamos rotar imgenes,
sino escalarlas. Las imgenes presentes
en la web suelen ser de gran tamao, y
nosotros queremos crear un mural de un
tamao esttico. Tendremos que adaptar
las imgenes descargadas para que quepan en el mural.

Para hacerlo vamos a insertar las imgenes en una mayor, pero hay muchas
maneras de hacer esto. La solucin que
adaptaremos en nuestro caso es la de
dividir la imagen-mural en tantos recuadros como imgenes vayamos a insertar.
Cmo sabremos la cantidad de cuadrculas? Pues escogeremos la menor
potencia de 2 que sea mayor que nuestro
nmero de imgenes. No es muy complicado; por ejemplo, si tenemos 7 imgenes, 8 (2 elevado a 3) ser suficiente.
Bsicamente multiplicaremos 2 por s
mismo hasta que sea mayor que el
nmero de imgenes que queramos mostrar. Grficamente lo que haremos ser ir
dividiendo en anchura y en altura la imagen en cuadrculas, en cada iteracin se
multiplicar por 2 el nmero de cuadrculas. Con este mtodo perderemos
espacio en la imagen, pero al ser tan sencillo no complicar mucho el cdigo.

Creemos el thumbnail
Primero creemos una imagen vaca, ver
Listado 1. La Figura 3 muestra el resultado. En la imagen vaca que creamos esta
vez no cargamos ninguna imagen, sino
que usamos el mtodo new() que necesita el tipo de pixel (RGB viene de Red>Rojo Green->Verde Blue->Azul, es
uno de los formatos estndar) y el tamao de la imagen medido en pixels. En
nuestro caso hemos escogido 600 pixels
de ancho por 480 de alto (presta atencin
a los (), porque la resolucin se expresa como una secuencia del tipo (x,y) ).
Esta nueva imagen no contiene nada, a
excepcin de un decepcionante fondo
negro. Vamos a poner algo de color!
Cogemos la imagen del daemon e
invocamos el mtodo thumbnail(), que
escala la imagen tanto vertical como
horizontalmente. Tenemos que pasarle el
tamao deseado como una secuencia; la
nueva imagen tendr un tamao de
300x200 pixels. Puede aceptar un parmetro adicional, en nuestro caso es
Image.ANTIALIAS, que debera mejorar
la resolucin de la nueva imagen.
A continuacin usamos el mtodo
paste() de Image, que nos permite
pegar una imagen dentro de otra en las
coordenadas indicadas como segundo
parmetro. Pegamos la imagen daemon dos veces, la primera en la posicin (0,0) del mural y la segunda en la
posicin (300,0). Podemos ver el resultado usando el mtodo show().

Figura 4: Llamaradas solares que amenazan


con dejar fuera de combate a los satlites de
comunicadciones.

El Fichero de Configuracin
Las URLs y la resolucin deben ser recogidas por el programa, pero cmo?
Existen varias opciones-- podramos
pasrselas al programa cuando se ejecute. Las URLs tienen el problema de ser
bastante largas en ocasiones, as que la
linea de comandos para ejecutar el programa puede ser engorrosa.
En lugar de eso vamos a usar un fichero de configuracin. Cada vez que el programa se ejecute, leer este fichero y
recoger los parmetros oportunos.
Qu forma tendr el fichero? La ltima tendencia es crear ficheros XML de
configuracin. Pero el XML puede ser
demasiado complicado si tenemos en
cuenta que nuestro fichero de
configuracin puede no tener ms de 10
lneas. En UNIX, la tendencia es la de
usar el formato de clave = valor, y ese
es el que usaremos. El fichero ser como
el que se muestra en el Listado 2.
Leeremos cada lnea del fichero, la
dividiremos usando el = y usaremos

Listado 2: collage.conf
01
02
03
04
05
06

[tamao]
horizontal = 800
vertical = 600

[imgenes]
url1 = http://www-mgcm.arc.
nasa.gov/MarsToday/marstoday.
gif
07 url2 = http://www.sec.noaa.
gov/sxi/current_sxi_4MKcorona.
png
08 url3 = http://www.ssec.wisc.
edu/data/sst/latest_sst.gif
09 url4 = http://www.wetterzentrale.de/pics/D2u.jpg

la primera parte como clave en un dicW W W. L I N U X - M A G A Z I N E . E S

PYTHON

71

LIBRERAS

PIL

cionario, y la segunda como valor. Si ya


existe la clave, usaremos una lista como
valor con los distintos valores como
entrada. Pero por qu vamos a realizar
nosotros el trabajo duro cuando alguien
ya lo ha resuelto?
Python trae en su distribucin estndar una librera que nos ser de enorme
utilidad. Alguien consider oportuno elaborar un analizador de archivos de
configuracin, se llama ConfigParser.
Con ella podemos extraer la informacin
del archivo de configuracin.
El archivo de configuracin se compone de Secciones y Opciones. Cada
seccin contiene varias opciones, y los
nombres de las secciones y opciones
deben ser nicos. Por eso las URLs
comienzan con url1, url2 y url3.
Pero esto no ser un problema, vemos
cmo funciona ConfigParser (ver Listado
3). Como podemos apreciar en el ejemplo, el uso de ConfigParser es muy sencillo. Primero se crea el analizador, guardndolo en la variable config. Despus
cargamos con el mtodo readfp() el
fichero de configuracin; este mtodo
tambin analiza el fichero. A partir de
ese momento podemos realizar preguntas al objeto almacenado en config. Con
sections() obtenemos una lista de las
secciones y con options() de las opciones. Con esa informacin ya podemos
recoger los datos necesarios usando el
mtodo get(), al que pasamos una seccin y una opcin.

Ensamblemos las Partes


Ahora ya tenemos:
Un sistema de configuracin, usando
ConfigParser.
Un sistema para descargar las imgenes, usando httplib.
Un sistema para manipular las imgenes, usando PIL.
Nos toca ahora unirlo todo para que
genere la pgina que aparece en la
Figura 5. El resultado final se puede descargar de [5].
Crearemos una clase Collage con los
mtodos
__cargaConf()
__descarga()
__totalXY()
generaCollage()
__generaImagen()
__generaHTML()
Cuando un mtodo comienza con __
se convierte en privado. Cualquier inten-

72

PYTHON

to de hacer uso de
ese mtodo generar
una Excepcin. Por
tanto, esos mtodos
no pueden ser invocados desde fuera
del objeto Collage.
De esta manera
Collage slo tiene un
mtodo
accesible
desde el exterior,
genera Collage(). Se
ha separado la generacin de HTML de
la del collage para
posibilitar las futuras extensiones del Figura 5: Nuestro panel de control espacial terminado y colocado en
objeto. Por ejemplo, una pgina web generada dinmicamente.
podramos no querer generar un fichero HTML sino incorComo siempre, se espera que el lector
porar la imagen en un programa. En tal
dedique algo de tiempo a jugar con el
caso heredaramos de Collage y crearaprograma para adaptarlo a sus necesidamos un nuevo mtodo generaCollage()
des o ideas.
que slo generase la imagen y la devolConclusin
viese.
La complejidad de un programa Python
El mtodo __generaHTML() genera el
no depende de la cantidad de lneas de
cdigo HTML de la pgina web. Un
cdigo que contenga, sino ms bien del
punto a resaltar es que genera un mapa
nivel al que trabaje. En el programa de
sobre el collage, de manera que sea
este artculo hemos hecho uso intensivo
posible pulsar sobre las distintas imgede libreras que han realizado acciones
nes que en l aparecen. Al hacerlo se
muy complicadas por nosotros. Python
cargar la imagen a tamao natural. El
posee una amplio abanico de libreras a
mapa se genera recorriendo el diccionaexplotar, muchas de ellas con aos de
rio de imgenes. Cada entrada del dicdesarrollo esperando a programadores con
cionario contiene un objeto de la clase
ideas originales que poner en prctica.
Imagen.
Imagen alberga la informacin de cada
imagen descargada mientras el programa
Recursos
la almacena. Se almacenan los datos pro[1] Grficos que usaremos:
pios de cada imagen, como por ejemplo
http://www-mgcm.arc.nasa.gov/Mars
las coordenadas que ocupar finalmente
Today/marstoday.gif
en el collage.
http://www.sec.noaa.gov/sxi/

Listado 3: Uso de ConfigParser


01 >>> config =
ConfigParser.ConfigParser()
02 >>> config.readfp(open('collage.conf'))
03 >>> config.sections()
04 ['tamao', 'imgenes']
05 >>>
06 >>> config.options('imagen')
07 ['url1', 'url3', 'url2']
08 >>>
09 >>>
config.get('imagen','url1')
10 '"http://www-mgcm.arc.nasa.
gov/MarsToday/marstoday.gif"'

current_sxi_ 4MKcorona.png
http://www.ssec.wisc.edu/data/sst/
latest_sst.gif
http://www.wetterzentrale.de/pics/
D2u.jpg
[2] Python Imaging Library:
http://www.pythonware.com/
products/pil/
[3] The Gimp: http://www.gimp.org
[4] Noticia sobre llamarada solar en
Slashdot:
http://science.slashdot.org/science/05/
09/08/1933205.shtml?tid=215&tid=14
[5] Listado del programa final de este
artculo: http://www.linuxmagazine.es/Magazine/Downloads/Es
peciales/06_Python

W W W. L I N U X - M A G A Z I N E . E S

Mechanize

LIBRERAS

Python y la Web

Enredados
Podemos automatizar comandos y programas grficos, por qu no
automatizar la interaccin con pginas web? En este artculo crearemos
un pequeo script que puede ahorrarnos mucho trabajo con el ratn.
Por Jos Mara Ruz

Digamos que llegas un da por la


maana a la oficina. El jefe se acerca y te
pide que vuelvas a pasar, otra vez!, un
montn de informacin a otra empresa a
travs de la peor interfaz jams diseada: una web.
Carga la pgina, introduce tus datos de
acceso, pincha aqu, pincha all. Cuando
ests en la pgina con el formulario en
cuestin debes introducir los datos y pinchar en un enlace o botn para enviarlos. Una y otra vez, una y otra vez. Quiz
durante horas.
Acaso no hay una mejor manera de
hacer esto? Lo ideal sera poder usar tu
hoja de clculo preferida, rellenar los
campos en ella de forma rpida (aque-

W W W. L I N U X - M A G A Z I N E . E S

llos que se repitan pueden ser copiados y


pegados) y cuando estuviese lista, hacer
algo (magia vud?) y que se cargasen
solos en la dichosa web. Y por supuesto,
sin que se enterase el jefe, as tendras
ms tiempo para leer artculos como ste
;).
Pues s, existe una manera de hacer
exactamente lo que acabas de leer! Y
vamos a explicarlo en este captulo. As
podrs decirle a tu jefe que esta revista
har a la empresa mucho ms productiva.

Mechanize
No es la primera vez que hago esto. Hace
algunos aos tuve este mismo problema

en la oficina en la que trabajaba. Haba


que rellenar un formulario web para dar
parte de las ventas. Esa tarea, debido a
su volumen, requera que una persona
perdiese toda una maana simplemente
porque a nadie en la otra empresa se le
ocurri la idea de hacer el proceso ms
rpido.
As que ni corto ni perezoso mi jefe
cre un script en Perl que haca este trabajo a partir de un fichero de texto CSV.
El problema es que lo hizo en Perl, y esta
seccin va sobre Python. Hemos de
mudarnos todos a Perl para poder disfrutar de este tipo de ventajas? No, gracias a
que John J. Lee decidi portar la librera
Mechanize que Andy Lester cre en Perl

PYTHON

73

LIBRERAS

Mechanize

(ver Recurso [1]) a Python (ver Recurso


[2]).
Las distribuciones de Linux suelen permitir instalarla como paquete. El problema es que la versin ms completa no
est an liberada, y es recomendable
emplear la que se encuentra en el servidor
de Subversion de John. El proceso es simple, instalamos el cliente de Subversion
que ms nos guste, aqu usar el estndar,
y descargamos el cdigo fuente con:
> svn co http://U
codespeak.net/svn/wwwsearch/U
mechanize/trunk mechanize

John emplea la librera setuptools de


Python para compilar e instalar la librera, as que deberamos proceder a instalarla antes de continuar. Despus slo
debemos ejecutar:
> python setup.py build
> sudo python setup.py install

Y listo. Ya tenemos nuestra librera


mechanize lista para trabajar.

Juguemos con una Web


Comencemos con algo simple. Vamos a
conectar con la web de Linux Magazine

y a pedirle que busque la palabra


python para, a continuacin, conseguir una lista de las urls de los primeros
artculos que contengan esa palabra
(sern enlaces a ficheros PDF), ver
Figura 1. De esta forma comprobaremos
cmo se trabaja con mechanize, ya que
tiene una forma peculiar de tratar el
cdigo HTML.
Para ello debemos comenzar por
arrancar python e importar la librera
mechanize y re (expresiones regulares):
>>> import re
>>> import mechanize

No ocurre nada particularmente vistoso,


a no ser que no hallamos instalado
correctamente la librera. Muy bien,
seguidamente necesitamos crear un
navegador:

09
10
11
12
13
14
15
16

import re
import mechanize
br = mechanize.Browser()
br.set_handle_robots(False)
respuesta =
br.open(http://www.linuxmagazine.es/)
br.select_form(nr=1)
br[words]=python
br.submit()
# Estamos en la pgina de
resultados

17
18 #primer resultado
19 urls = [url.absolute_url for
url in
br.links(url_regex=re.compile(rpdf$))]

74

PYTHON

>>> br.set_proxies(U
{http : 192.168.1.254:8000,U
ftp : 192.168.1.254:8000})

Aqu he especificado un proxy para http


y otro para ftp, que suele ser lo normal.
Ya tenemos nuestro navegador listo. Slo
tenemos que abrir una pgina:
>>>> respuesta= br.openU
(http://www.linuxmagazine.es/)

>>> br = mechanize.Browser()

Ahora ya lo tenemos en la variable br.


No me refiero a un navegador grfico,
sino a todo lo que un navegador puede
hacer pero sin la parte grfica. Me
explico, un navegador posee un motor
que interacta con los servidores web y
una interfaz grfica que interacta con el

Listado 1: Nuestra Araa web


01
02
03
04
05
06
07
08

usuario. La librera mechanize nos da lo


primero sin lo segundo. Nuestra interfaz de usuario sern llamadas a mtodos del objeto Browser.
Un problema que nos podemos encontrar, ya desde el principio, es que nuestra
red disponga de un proxy para acceder a
Internet. Este caso es bastante comn,
as que debemos indicrselo al objeto
almacenado en br:

20
21 #eliminamos duplicados
22
23 urls =
dict(zip(urls,urls)).keys()
24
25
26 r =
re.compile(.*/(\d+)/(.*)$)
27
28
29 for url in urls:
30
31 m= r.match(url)
32 nombre =
m.group(1)+-+m.group(2)
33 print nombre
34
35 respuesta = br.open(url)
36 datos = respuesta.read()
37
38 fichero = open (nombre,w)
39
40

fichero.write(datos)
fichero.close()

Una vez abierta se nos devuelve un


objeto de respuesta. Este objeto contiene
todos los mtodos necesarios para poder
trabajar con la informacin devuelta por
el servidor web. Por ejemplo, podramos
imprimir el contenido HTML de la
pgina:
>>>> printU
respuesta.read()
<html>....

No pongo aqu la informacin devuelta


porque podra ocupar una pgina completa. Adems es de poca utilidad. Lo
interesante de mechanize es que genera
la pgina y nos permite acceder a las partes jugosas de la misma de forma muy
sencilla. Digamos que queremos saber
qu formularios contiene la pgina:
>>> for form in br.forms():
...
print form
...
<POST http://U
www.linux-magazine.es/Readers/U
Newsletter/reply application/U
x-www-form-urlencoded
<HiddenControlU
(subject=subscribe)U
(readonly)>
<TextControl(email=Tu email)>
<SubmitControl(<None>=OK)U
(readonly)>>
<POST http://U
www.linux-magazine.es/U

W W W. L I N U X - M A G A Z I N E . E S

Mechanize

LIBRERAS

Figura 1: Pgina de resultados de bsqueda de Linux Magazine.

search application/U
x-www-form-urlencoded
<TextControl(words=)>>

Mechanize tiene su propio lenguaje para


representar partes de la pgina, y aqu
podemos ver un ejemplo del mismo.
Nos dice que hay dos formularios, que
emplean POST como mtodo de comunicacin con la pgina. Muy bien, nosotros queremos acceder al segundo
puesto, que es el que emplea la url
http://www.linuxmagazine.es/s earch.
Este formulario tiene un campo de texto
llamado words. Al principio cuesta un
poco entender este lenguaje, pero con
un poco de prctica no es tan complicado.
De acuerdo, tenemos que acceder al
segundo formulario, as que le indicamos
a br, nuestro navegador virtual, que
emplee este formulario. Es posible realizar
la seleccin por posicin o por nombre. El
nombre vendra indicado por el parmetro
HTML name, que el desarrollador de la
web de Linux Magazine ha decidido ignorar, al fin y al cabo no es obligatorio.
Si se diese el caso de que el formulario
tuviese un nombre, podramos seleccionarlo con:

W W W. L I N U X - M A G A Z I N E . E S

>>> br.select_formU
(name = miformulario)

Pero como no es este nuestro caso, lo


seleccionaremos por posicin:
>>> br.select_form(nr = 1)

Empleamos el nmero 1, porque los formularios estn numerados comenzando


en 0. Con este mtodo ya tenemos seleccionado el formulario, pero ste se compone a su vez de varios elementos
incrustados. Cmo podemos seleccionarlos? Por suerte para nosotros, John, el
desarrollador, ha empleado toda la
potencia de Python y ha realizado un
truco de magia: hacer que el objeto
almacenado en br se comporte como un
tipo diccionario Python. Si el elemento
input donde hay que escribir las palabras
a buscar se llama words, entonces
todo lo que tenemos que hacer es:
>>> br[words]=python

As de simple! Es o no maravilloso este


mdulo? Esto s que es cdigo compacto
en estado puro. Despus de la euforia
debemos volver al asunto que nos ha tra-

do hasta aqu: queremos los enlaces con


la palabra python. Ya tenemos nuestro
formulario relleno, ahora debemos pulsar el botn. Pero cmo? Pues con el
mtodo submit:
>>> respuesta = br.sumbit()

Lo que acabamos de hacer es ms complejo de lo que parece, qu ha ocurrido?


Al ejecutar submit hemos enviado los
datos del formulario al servidor, que nos
habr respondido redirigindonos a la
pgina con los resultados. El contenido
de esta interaccin se almacena en respuesta, que no es ni ms ni menos que
otro objeto que envuelve un documento
HTML. Debemos recoger todos los enlaces que nos interesan.
Y aqu viene otro punto fuerte de
mechanize: su integracin con las expresiones regulares. Desde luego que John
no nos iba a fallar en este aspecto. Podemos elegir un enlace usando una expresin regular, de forma que no tenemos
que ir buscando a tontas y a locas. Con
saber ms o menos qu formato tendr
el enlace que deseamos, podremos conseguir la informacin que contiene. Pero
antes veamos qu deberamos hacer si

PYTHON

75

LIBRERAS

Mechanize

no supiramos muy bien qu buscamos.


Al igual que con los formularios, los
enlaces se pueden recorrer como si fuesen una lista:
>>> for link in br.links():
...
print link
...
Link(base_url=http://U
www.linux-magazine.es/,U
url=/,text=logoOL.gif[IMG],U
tag=a, attrs=[(href, /)])
Link(base_url=http://U
www.linux-magazine.es/,U
url=/,text=logoOR.gif[IMG],U
tag=a, attrs=[(href, /)])
....

Aqu slo se muestran los dos primeros.


Si pruebas esto mismo en tu equipo
vers que hay un nmero respetable de
enlaces en esta pgina en concreto. De
nuevo mechanize nos muestra lo que
entiende por un enlace. Pero como nosotros sabemos lo que queremos, podemos
pasar directamente a la accin con las
expresiones regulares:
>>> urls =U
[url.absolute_url for url inU
br.links(url_regex=re.compileU
(rpdf$))]

Python comprime en poco cdigo mucho


trabajo, as que esta nica lnea requiere
una explicacin. Comencemos por la
lista de compresin. En Python es posible crear listas a partir de definiciones de
lo que se supone que va en las mismas.
En este caso hay que comenzar por el
cdigo:
br.links(url_regex=U
re.compile(rpdf$))

Este cdigo localiza todos aquellos enlaces que se puedan identificar con el
argumento que pasemos al mtodo
br.links(). Es posible usar un nmero,
como hicimos con el formulario anteriormente, pero en lugar de eso vamos a
emplear una expresin regular. As que
usamos la funcin re.compile(), que no
hace otra cosa que generar un objeto
que contiene un reconocedor de la
expresin regular que pasamos como
parmetro. En nuestro caso es pdf$,
(la r de delante le indica a la funcin
que la cadena se corresponde con una

76

PYTHON

expresin regular), que no hace otra


cosa que localizar cadenas acabadas en
la letras pdf, y para ello podemos el
smbolo $ al final de pdf. Como
decamos, re.compile() devuelve un
objeto que reconoce la expresin regular, lo que podramos llamar una expresin regular compilada, y lo almacenamos en el argumento con nombre
url_regex. La funcin br.links() lo reconoce y sabe que debe buscar enlaces
que al reconocerlos con la expresin
regular devuelvan True.
br.links() generar as una lista de
enlaces, y aqu entra en funcin la clusula for ... in ..., que no hace otra cosa
que recorrer la lista y devolver los enlaces bajo el nombre de variable url. La
lista de compresin se compone de cada
uno de esos enlaces, con nombre url, de
lo que nos quedamos con su atributo
absolute_url: su ruta completa. Y con
esto acabamos. Todo se reduce a una
sola lnea de Python, escribimos poco
pero vale por decenas de lneas!
An tenemos un problema, la web de
resultado de bsqueda de Linux Magazine devuelve los resultados duplicados.
Aplicando un poco de Kung Fu Python
podemos deshacernos de ellos en una
lnea:
>>> urls = dict(U
zip(urls,urls)).keys()

Zip significa cremallera en ingls, y eso


es precisamente lo que hace la funcin
zip(). Cierra dos listas como si fuese una
cremallera:
>>> zip([uno,dos,U
tres],[1,2,3])
[(uno,1),(dos,2),U
(tres,3)]

Esto puede resultar muy conveniente,


porque precisamente una lista con tuplas
de 2 valores es lo que necesitamos para
crear un diccionario.
>>> dict(zip([uno,dos,U
tres],[1,2,3]))
{dos: 2, tres: 3,U
uno: 1}

Y del diccionario podemos obtener las


llaves usando el mtodo keys(). Si hacemos todo esto con un lista, cerrndola
con ella misma en cremallera, y teniendo

en cuenta que en un diccionario no pueden existir dos llaves iguales, el resultado es que al extraer las llaves obtenemos la misma lista pero eliminando los
duplicados.
Complicado? S, como las lneas anteriores, pero indudablemente til y que
requiere muy pocas pulsaciones del
teclado.
De acuerdo, ya tenemos los enlaces
Y qu hacemos con ellos ahora? Pues
podramos descargarlos: recorriendo la
lista y usando la funcin br.open() para
cargarlos, y la respuesta.read() para leer
el contenido del fichero, guardndolos
en un fichero con nombre igual a la
ltima parte de la URL. Por desgracia,
muchos
de
ellos
se
llaman
Python.pdf, as que vamos a usar el
nmero de esa revista en el nombre.
Puedes ver el cdigo completo en el Listado 1.
Pero, con este cdigo slo podemos
conseguir los primeros resultados no?
S, para mejorarlo y que descargue todos
los resultados slo tendramos que localizar el enlace a siguiente resultado y
pulsar
en
l
con
el
mtodo
br.follow_link(). Dejo al lector que
piense cmo hacerlo, dando una sola
pista: emplea un bucle hasta que no
encuentres links que se correspondan
con la expresin regular.

Conclusin
No es de extraar que Google utilice
Python. De hecho, el propio Guido Van
Rossum cre una araa web con una de
las primeras implementaciones de
Python ya hace algunos aitos. Hemos
podido comprobar cmo podemos usar
una pgina web como si estuvisemos
delante de un navegador mediante la
magnfica, y an en estado Beta, librera
mechanize, y empleando un nmero de
lneas de cdigo realmente minsculo.
La prxima vez que el lector se enfrente
a un trabajo tedioso con una pgina web,
puede que tenga un par de ideas para
hacer que el ordenador trabaje por l
gracias a cierta serpiente.

Recursos
[1] Mechanize para Perl: http://search.
cpan.org/dist/WWW-Mechanize/
[2] Mechanize para Python: http://
wwwsearch.sourceforge.net/
mechanize/

W W W. L I N U X - M A G A Z I N E . E S

ReportLab

LIBRERAS

Generacin de informes profesionales desde Python

ReportLab

Hoy en da se hace imprescindible disponer de herramientas que permitan generar informes en PDF de alta
calidad rpida y dinmicamente. Existen diferentes herramientas para esta finalidad, entre ellas cabe destacar
ReportLab, biblioteca gratuita que permite crear documentos PDF empleando como lenguaje de programacin Python. Por Ana M. Ferreiro y Jos A. Garca
La biblioteca ReportLab crea directaque hay que seguir para instalar y confimente documentos PDF basndose en
gurar ReportLab.
comandos grficos y sin pasos intermeEl paquete pdfgen es el nivel ms bajo
dios, generando informes en un tiempo
para generar documentos PDF, que se
extremadamente rpido y
basa esencialmente en
siendo de gran utilidad en
una secuencia de instruclos siguientes contextos:
ciones para dibujar cada
generacin dinmica de
pgina del documento. El
PDFs en aplicaciones web
objeto que proporciona las
(empleado con Zope),
operaciones de dibujo es
generacin de informes y
el Canvas. El Canvas mide
publicacin
de
datos
igual que una hoja de
almacenados en bases de
papel blanco, con puntos
datos, embebiendo el
sobre la misma identificamotor de impresin en
dos mediante coordenadas
aplicaciones para consecartesianas (X,Y), que por
guir la generacin de
defecto tienen el origen
informes a medida, etc.
Figura 1: Coordenadas carte- (0,0) en la esquina infesianas de una hoja.
rior izquierda de la pgiPrimeros Pasos
na. La coordenada X va
Lo primero es tener instalados Python y
hacia la derecha y la coordenada Y
ReportLab para realizar todas las prueavanza hacia arriba (ver Figura 1).
bas que van surgiendo y las que se nos
Para crear nuestro primer PDF basta
ocurran. En [1] se detallan los pasos
escribir en un fichero, que podemos lla-

W W W. L I N U X - M A G A Z I N E . E S

mar ejemplo1.py, las siguientes lneas


de cdigo:
from reportlab.pdfgenU
import canvas
c=canvas.Canvas("primer.pdf")
c.drawString(50,500, " MiU
PRIMER PDF")
c.drawString(250,300,U
"Coordenada=(250,300) ")
c.drawString(350,200,U
"(350, 10)")
c.drawString(150,400,U
"Aprendiendo REPORTLAB")
c.showPage()
c.save()

Probamos el programa y vemos que en


el mismo directorio ya se ha creado un
fichero llamado primer.pdf, anlogo al
que se muestra en la Figura 2, sin necesidad de realizar ningn otro paso intermedio. Mediante la lnea from reportlab.pdfgen import canvas importamos

PYTHON

77

LIBRERAS

ReportLab

Canvas, utilizado para dibujar en el


PDF. El comando canvas.Canvas
(path__fichero) permite indicar el nombre con el que se guardar el PDF. El
mtodo draw String(x,y,cadena_texto)
empieza a escribir el texto en la coordenada (x,y) (se puede probar a cambiar
las diferentes coordenadas). El mtodo
showPage() crea la pgina actual del
documento. Finalmente, save() guarda
el fichero en el path indicado.
En el ejemplo previo hemos creado
un PDF sin especificar el tamao del
documento, si queremos fijar el tamao
de la hoja (A4, letter, A5, etc.) bastara
indicarlo en el Canvas mediante:
from reportlab.lib.U
pagesizes import letter,A4,A5,U
A3
c=canvas.Canvas("primer.pdf",U
pagesize=letter)

El tamao de las hojas se importa


mediante from reportlab. lib.pagesizes
import letter, A4,A5,A, y se especifica
en el Canvas con la propiedad pagesize.

Listado 1: ejemplo2.py
01 from reportlab.pdfgen import
canvas
02 c=canvas.Canvas
("canvas_draw.pdf")
03 c.setFont("Helvetica",24)
04 c.line(50,50,50,350)
05 c.line(50,50,350,50)
06 c.setStrokeColorRGB(1,1,0.0)
07 c.setFillColorRGB(0,0.0,0.5)
08 c.roundRect
(75,75,275,275,20,stroke=0,
fill=1)
09 c.setFillColorRGB(0.8,0.,0.2)
10 c.circle (205,205,100,stroke=1,fill=1)
11 c.setFillColorRGB
(0.75,0.75,0.)
12 c.drawString
(125,80,"Cuadrado")
13 c.setFillColorRGB(0,1,0.2)
14 c.drawString
(155,200,"Circulo")
15 c.setStrokeColorRGB(1,0,0.0)
16 c.ellipse
(75,450,350,335,fill=0)
17
18
19
20

78

c.setFillColorRGB(0,0,0.5)
c.drawString(150,375,"Elipse")
c.showPage()
c.save()

PYTHON

Muchas veces querremos


adaptar el dibujo a las
dimensiones de la hoja, por
lo que necesitamos conocer
el ancho y el alto de la
misma:
ancho=
tipo
_hoja[0] y alto=tipo_hoja
[1]; donde tipo_ hoja puede
ser letter, A4, A5 , etc.
La clase Canvas dispone
de diferentes herramientas
para dibujar lneas, circunferencias,
rectngulos,
arcos, etc. Adems permite Figura 2: Primer documento generado. El resultado impreso
modificar el color de los refleja cmo controlar las coordenadas de una hoja.
objetos, rotar, trasladar,
indicar tipo y tamao de fuente, etc. En
ReportLab encontraremos muchas
el cdigo del Listado 1 podemos ver
otras).
cmo se dibujan, por ejemplo lneas,
Aadiendo Imgenes
mediante canvas.line (x1,y1,x2 ,y2);
En este instante ya podemos demostrar
crculos,
empleando
el
mtodo
nuestra creatividad en dibujo artsticanvas.circle (x_centro,y_ centro, radio,
co, aunque de un modo bastante labostroke=1, fill=1); y rectngulos con
rioso. Seguro que ms de uno preferiesquinas redondeadas, con canvas.
mos incluir en nuestros ficheros imgeround Rect (x,y, ancho, alto ,angulo,
nes ya creadas. Pues esto es posible: en
stroke =1,fill=0). Ntese que cada vez
el rea de descarga de Linux Magazine
que se quiera emplear un color nuevo
tenemos la imagen Tux2.png para las
hay que indicarlo mediante canvas
diferentes pruebas.
setFillColor RGB(r,g,b), para el color de
A la hora de incluir imgenes poderelleno, o canvas. setStroke ColorRGB
mos optar por las siguientes opciones.
(r,g,b), para fijar el color de las lneas.
La primera y ms sencilla, pero con la
La eleccin del tipo de fuente se realiza
que no nos es posible rotar, trasladar, ni
usando canvas .SetFont (tipo_ fuente,
redimensionar es mediante el mtodo
tamao). En la Figura 3 se muestra el
drawImage (image,x,y,width =None,
PDF que creamos con el simple cdigo
height=None) de la clase Canvas (si no
del Listado 1. Podemos probar a camse especifica el alto y el ancho, coloca la
biar propiedades (en el manual de

Figura 3: Objetos que se pueden dibujar con un Canvas.

W W W. L I N U X - M A G A Z I N E . E S

LIBRERAS

ReportLab

Figura 4:
drawImage.

Colocando

imgenes

con

Figura 5: Modo en que se rota, traslada y


escala un objeto Drawing.

figura con sus dimensiones originales).


Mediante las siguientes lneas podemos
crear un fichero similar al de la Figura 4.

se instancia la clase; texto contiene el


texto del prrafo, en el que se eliminan
los espacios en blanco innecesarios;
bulletText indica si el prrafo se escribe
con un punto al inicio del mismo; la
fuente y otras propiedades del prrafo y
el punto se indican mediante el argumento style. Veamos cmo aadir un prrafo:

c.drawImage("Tux2.png",0,0)
c.drawImage("Tux2,png",200,300,U
width=30,height=60)

Si lo que pretendemos es rotar imgenes


o escalarlas, debemos emplear los objetos Image(x,y,ancho,alto,path_imagen)
y Drawing(ancho,alto) que se importan
mediante from reportlab.graphics.shapes
import Image, Drawing. El objeto Drawing puede escalarse, rotarse y trasladarse; pero hay que tener en cuenta que
todas estas operaciones son acumulativas (ver Figura 5). En el Listado 2 podemos ver cmo emplear correctamente
estos objetos (Figura 6). Obsrvese que
ahora el PDF no se genera a partir de un
Canvas, sino que se genera mediante
renderPDF.drawToFile
(d,"canvas_
image 2.pdf"), donde d=Drawing
(A4[0] ,A4[1]). Podemos probar a modificar los valores de los distintos mtodos
scale, rotate, translate; observaremos
que a veces la imagen puede desaparecer del folio, eso es debido a que los
valores que se dan hacen que nos salgamos de las dimensiones de la pgina.

Creacin de Prrafos y Tablas


La clase reportlab.platypus.Paragraph
permite escribir texto formateado (justificado, alineado a la derecha o izquierda,
centrado) en un aspecto elegante, adems de modificar el estilo y color de trozos de la lnea a travs de XML. Mediante
Paragraph(texto,style, bullet Text=None)

W W W. L I N U X - M A G A Z I N E . E S

01 from reportlab.lib.styles
import getSampleStyleSheet
02 styleSheet=getSampleStyle
Sheet()
03 story=[]
04 h1=styleSheet['Heading1']
05 h1.pageBreakBefore=0
06 h1.keepWithNext=1
07 h1.backColor=colors.red
08 P1=Paragraph("Estilo Cabecera
- h1 ",h1)
09 story.append(P)
10 style=styleSheet['BodyText']
11 P2=Paragraph("Estilo
BodyText"

Figura 6: Ejemplo de rotacin, traslacin y


escalado de imgenes.

,style)
12 story.append(P2)

El paquete reportlab.lib.styles contiene


estilos predefinidos. Con getSample
Style Sheet obtenemos un estilo ejemplo. Tenemos un estilo para la cabecera
y otro para el texto normal. Mediante
h1.pageBreakBefore=0 decimos que no
queremos un salto de pgina cada vez
que se escriba una cabecera h1, en caso
contrario basta escribir 1. Los diferentes
prrafos se van almacenando en la lista
story porque posteriormente se aaden
al pdf a travs el paquete SimpleDoc
Template de reportlab.platypus:
doc=SimpleDocTemplate(U
"paragrahp.pdf", pagesize=A4,U
showBoundary=1)
doc.build(story)

En este caso se genera un PDF con tantas pginas como sea necesario.

Listado 2: Jugando con Imagenes (ejemplo3_2.py)


01 from reportlab.graphics.shapes
import Image, Drawing

11 IMAGES.append(d)

02 from reportlab.graphics import


renderPDF

13 d.add(img)

03 from reportlab.lib.pagesizes
import A4

15 d.scale(2,2)

04 inpath="Tux2.png"

17 IMAGES.append(d)

05 IMAGES=[]

18 d=Drawing(A4[0],A4[1])

06 d=Drawing(80,100)

19 for img in IMAGES:

07 img=Image(200,0,80,100,inpath)

20

08 d.add(img)

21 renderPDF.drawToFile(d,"canvas_image2.pdf")

09 d.rotate(45)

12 d=Drawing(80,100)
14 d.translate(10,0)
16 d.rotate(-5)

d.add(img)

10 d.scale(1.5,1.5)

PYTHON

79

LIBRERAS

ReportLab

['Enero',1000, 2000],U
['Febrero',3000,100.5],U
['Marzo',2000,1000],U
['Abril',1500,1500]]

En una tabla se puede fijar el estilo de


cada miembro de la misma. Por ejemplo, si deseamos que el texto de la primera columna sea azul, y que los
nmeros sean todos verdes, haremos
t.setStyle([U
('TEXTCOLOR',(0,1),(0,-1),U
colors.blue), ('TEXTCOLOR',U
(1,1), (2,-1),colors.green)])

Figura 7: Ejemplo de prrafo, tabla e imagen.

Comprobar esto es tan sencillo como


tener un texto muy largo. Si aadimos
un prrafo cuyo texto sea "Hola"*300,
seguro que se generan ms de una
hoja.
Si lo que queremos es aadir una
tabla, es necesario importar from reportlab .platypus import Table, TablsStyle.

Una tabla se crea aadiendo una lista de


listas, donde cada componente de la
lista guarda la informacin de cada fila.
Si queremos construir una tabla de 5
filas y 3 columnas hacemos
t=Table([['','Ventas',U
'Compras'],U

En el cdigo del Listado 3 mostramos


un ejemplo. En l se ilustra cmo
adaptar el estilo segn se quiera
(Figura 7). Podemos ver que para
incluir un nuevo elemento en el PDF
es suficiente con ir aadiendo cada
objeto a la lista story.
Ahora ya sabemos todo lo necesario
para crear nuestros propios carteles,
informes, catlogos, presentaciones,

etc.

Recursos
[1] Reportlab:
http://www.reportlab.org

Listado 3: Crear Tablas y Prrafos (ejemplo4.py)


01 from reportlab.lib.pagesizes
import A4
02 from reportlab.lib.styles
import getSampleStyleSheet,
ParagraphStyle
03 from reportlab.platypus import
Spacer,
SimpleDocTemplate, Table,
TableStyle
04 from reportlab.platypus import
Paragraph, Image
05 from reportlab.lib import
colors
06
07 styleSheet=
getSampleStyleSheet()
08 story=[]
09 h1=styleSheet['Heading1']
10 h1.pageBreakBefore=0
11 h1.keepWithNext=1
12 h1.backColor=colors.red
13 h2=styleSheet['Heading2']
14 h2.pageBreakBefore=0
15 h2.keepWithNext=1

80

PYTHON

16 P=Paragraph("Estilo Cabecera h1 ",h1)


17 story.append(P)
18 P=Paragraph("Estilo h2 ",h2)
19 story.append(P)
20 style=styleSheet['BodyText']
21 texto=" Texto escrito para ver
como crear ficheros PDF."+\
22 "Este parrafo esta escrito
en estilo BodyText"
23 texto_largo=texto
24 #texto_largo=texto*100
25 P=Paragraph(texto_largo,style)
26 story.append(P)
27 story.append(Spacer(0,12))
28
29 t=Table([['','Ventas',
'Compras'],
30
['Enero',1000, 2000],
31
['Febrero',3000,100.5],
32
['Marzo',2000,1000],
33
['Abril',1500,1500]]
34
)
35

36 story.append(t)
37
38 story.append(Spacer(0,15))
39 P=Paragraph("Cabecera h1",h1)
40 story.append(P)
41
42 cadena='''Mediante ReportLab es
43
posible generar ficheros PDF
44
de gran calidad. Es posible
45
incluir graficos, imagenes,
46
tablas; creando informes
47
de gran calidad'''
48 P=Paragraph(cadena,style)
49
50 story.append(Spacer(0,15))
51
52 img=Image ("Tux2.png",
width=80,height=100)
53 story.append(img)
54 doc=SimpleDocTemplate("paragrahp.pdf",pagesize=A4,showBoun
dary=1)
55 doc.build(story)

W W W. L I N U X - M A G A Z I N E . E S

SERVICIO

Autores / Contacto

Autores
Ana Mara Ferreiro
Jose Antonio Garca
Jos Mara Ruz
Frank Wiles
Paul C. Brown
Pedro Orantes

Datos de Contacto
65, 75
65, 76
6, 15, 10, 19, 23, 28, 33, 39, 43, 48, 55, 59, 69, 73
52
3, 10
28, 33, 59, 69

Director
Paul C. Brown
Coolaboradores
Paul C. Brown, Jos Mara Ruz
Traductores
Paqui Martn Vergara, Lucas Gonzlez, Vctor
Tienda
Maquetacin
Miguel Gmez Molina
Diseo de Portada
Paul C. Brown
Publicidad
www.linux-magazine.es/pub/
Para Espaa
Marketing y Comunicaciones
anuncios@linux-magazine.es
Tel.:
(+ 34) 952 020 242
Fax.:
(+ 34) 951 235 905
Para el Resto del Mundo
Petra Jaser
ads@linux-magazine.com
Tel.:
(+49) 8999 34 11 23
Fax.:
(+49) 8999 34 11 99
Director Editorial
Paul C. Brown
Jefe de Produccin
Miguel Gmez Molina
Subscripciones:
www.linux-magazine.es/magazine/subs
Precios Subscripcin
Espaa:
54,90
Europa:
64,90
Resto del Mundo - Euros:
84,90
subs@linux-magazine.es
Tel.:
(+34) 952 020 242
Fax.:
(+34) 951 235 905
Linux Magazine
Linux New Media Spain, S.L.
Edfco. Hevimar, Planta 2, Ofic. 16
C/Graham Bell n 6
29590 - Mlaga
ESPAA
info@linux-magazine.es
Tel.:
(+34) 952 020 242
(+34) 951 235 904
Fax.:
(+34) 951 235 905
www.linux-magazine.es - Espaa
www.linux-magazine.com - Mundo
www.linux-magazine.co.uk - Reino Unido
www.linux-magazine.com.br - Brasil
www.linux-magazine.pl - Polonia
Si bien se toman todas las medidas posibles para
garantizar la precisin del contenido de los artculos
publicados en Linux Magazine, la editorial no se
hace responsable de imprecisiones aparecidas en la
revista. Asimismo, Linux Magazine no comparte
necesariamente las opiniones vertidas por sus
colaboradores en sus artculos. El riesgo derivado
del uso del DVD y el material que contiene corren
por cuenta del lector. El DVD es estudiado escrupulosamente para confirmar que est libre de virus y
errores.
Copyright y Marcas Registradas 2012 Linux New
Media Spain, S.L. Linux New Media Spain S.L.
prohbe la reproduccin total o parcial de los contenidos de Linux Magazine sin su permiso previo y
por escrito. Linux es una Marca Registrada de Linus
Torvalds.

82

PYTHON

W W W. L I N U X - M A G A Z I N E . E S

You might also like