Sie sind auf Seite 1von 84

DVD GRATIS

Debian 6

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

PROYECTOS COMPLETOS
17 Y FUNCIONALES Especia
l

06 BSICOIntroduccin 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

Denis Babenko - 123RF.com


00006

Automatiza formularios web


Tu Gua Prctica Integra Python con Java, .Net y Qt
Aprende de desarrolladores Ampla Open/LibreOffice
8 413042 594529

profesionales y de sus Crea y manipula PDFs


ejemplos del mundo real Maneja grandes volmenes de datos

WWW.LINUX- MAGAZINE.ES
En la tienda de Linux Magazine (www.linux-magazine.es/tienda) vendemos revistas y libros que pue-

den 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 pue-

des conseguir software, gadgets, libros y servicios. Este mes en nuestra tienda...

Manual LPIC-1
El nico manual en castellano para la certificacin com-
pleta LPIC-1 (exmenes 101 y 102). Ms de 250 pginas
de ejemplos reales tomados de ambos exmenes expli-
cados 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 Inter-
national y con la garanta de Linux Magazine.

La gua perfecta en castellano para preparar el exa-


men para cualquier persona que tenga conocimien-
tos 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.


Debian 6 DVD/EDITORIAL

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. Gra-
cias 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 exis-
tencia en el lenguaje favorito para scripts, aun-
que tambin para grandes infraestructuras y
aplicaciones.
As, la mayor parte de los servicios de Goo-
gle 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 rendi-
miento 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


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
Formato del Cdigo en este Especial Debian la ms completa de las distros
Aunque la norma en el sangrado de Se reproduce as en los artculos: GNU/Linux, podrs encontrar en sus
cdigo en Python es que las tabulaciones for i in range (1, 5): repositorios todas las herramientas,
a principio de lnea sean mltiplos de cua- if ((i % 2) == 0): mdulos e infraestructuras que necesita-
tro espacios, para favorecer la legibilidad print i, es par rs para seguir los artculos de este
de los listados en este especial, cada tabu- else: especial.
lacin se ha reducido a un solo espacio. print i, es impar
De esta manera, cdigo que normal- A pesar de ser tcnicamente correcto,
mente se escribira como: animamos a que, si se transcribe cdigo
Recursos
for i in range (1, 5): para su ejecucin, se utilice la conven- [1] Todo el cdigo de este especial: http://
if ((i % 2) == 0): cin de los 4 espacios. www.linux-magazine.es/Magazine/
print i, es par En todo caso, todo el cdigo est dispo- Downloads/Especiales/06_Python
else: nible, con su formato convencional, en el [2] Debian: http://www.debian.org/index.
print i, es impar sitio de Linux Magazine en [1]. es.html

W W W. L I N U X - M A G A Z I N E . E S PYTHON 3
CONTENIDO Python 01

Introduccin Avanzado Integracin

06 Primeros Pasos 19 Sin Nombre 33 Limpieza Total


Python es un lenguaje potente, Python es un lenguaje de programa- AJAX es la palabra de moda, Google
seguro, flexible pero sobre todo sen- cin multiparadigma, y las funciones usa AJAX, Yahoo usa AJAX todo el
cillo y rpido de aprender, que nos lambda son parte fundamental de l, mundo quiere usar AJAX pero lo
permite crear todo lo que necesitamos aunque como veremos, existen bue- usas t? y ms importante an qu
en nuestras aplicaciones de forma gil nas razones para no abusar de ellas. demonios es AJAX?
y eficaz

Integracin

23 Python no hay ms que UNO


Has visto alguna vez a los brokers de
bolsa y sus sofisticados y caros pro-
gramas para ver las cotizaciones de
las empresas en bolsa en tiempo real?
Nosotros haremos lo mismo con
Python, OpenOffice y la tecnologa
10 lbum Fotogrfico UNO de OpenOffice.
Siguiendo con nuestro paseo por
Python, vemos caractersticas bsicas, 39 De Serpientes y Primates
y concretamente en este artculo, el .NET est avanzando, y Python no se
tratamiento de ficheros creando un ha quedado atrs. En lugar de comba-
programa para la ordenacin de colec- tirlo, ha entrado en simbiosis con ella.
ciones de fotos. Con Ironpython podremos hacer uso
de toda la potencia de .NET desde
15 Desparasitando Serpientes nuestro lenguaje favorito.
Da igual lo buenos programadores
que seamos, tarde o temprano dare- 43 Desarrollo Rpido!
mos con ese BUG que ser nuestro Ha llegado el cliente y te lo ha dejado
peor enemigo. Veamos cmo pode- claro: necesita el programa para ayer.
mos emplear herramientas para derro- Ha surgido un problema enorme y es
tarlo con mayor facilidad. necesario resolverlo en tiempo rcord.
La desesperacin se palpa en el
28 Cuando los Mundos Chocan ambiente y todos los ojos miran a tu
Otras Secciones Os descubrimos Jython, la forma mas persona. Devuelves una mirada de
sencilla de desarrollar vuestras aplica- confianza y dices con tono tranquilo:
03 DVD Debian 6
ciones Java como si las programrais No te preocupes, tengo un arma
82 Informacin de Contacto con Python. secreta para acabar con el problema.

4 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Python 01 CONTENIDO

VERS
IN
DVD
GNO
ME L
IVE

Infraesctructuras Libreras

48 Pyramid 65 Grficas 3D
Uno de los rivales de peso de Django Crear grficos 3D no es nada difcil en
est creciendo en popularidad poco a Python... sobre todo si tenemos a
poco. 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 infraestruc-
tura web.

55 Seriales
Hacer que distintos servicios se comu- 69 Vigilantes del planeta
niquen entre ellos es todo un pro- Quin no ha querido alguna vez sen-
blema que Facebook ha tratado de tirse como esos informticos de la
solucionar con Thrift. NASA en su centro de control? Hoy
nos construiremos el nuestro y contro-
laremos el planeta y sus alrededores.

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

77 ReportLab
Libreras Hoy en da se hace imprescindible dis-
poner de herramientas que permitan 3
generar informes en PDF de alta cali- n pg.
e
59 Cuaderno de Bitcora dad rpida y dinmicamente. Existen
macin
r
Te acuerdas de cuando cambiaste la diferentes herramientas para esta fina- info
versin de Firefox por ltima vez? y lidad, entre ellas cabe destacar Repor- Ver
de por qu instalaste ese programa tan tLab, biblioteca gratuita que permite
raro que parece no servir para nada ? crear documentos PDF empleando
Yo tengo mala memoria, as que uso como lenguaje de programacin
un cuaderno de bitcora. Python.

W W W. L I N U X - M A G A Z I N E . E S PYTHON 5
INTRODUCCIN Primeros Pasos

Anita Patterson - morguefile.com


Aprende a programar con este lenguaje de programacin multiplataforma

Primeros Pasos
Python es un lenguaje potente, seguro, flexi-

ble 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 Los programas escritos en este lenguaje de una comunidad de desarrolladores
Python es interesante, por qu es tan tienden a ser fciles de comprender y, cada vez ms amplia. Por ejemplo, per-
famoso y cada vez ms utilizado. Mirando por tanto, de mantener por terceros. Una mite la declaracin dinmica de varia-
un poco por Internet se pueden encontrar enorme ventaja frente a lenguajes como bles, es decir, no tenemos que declarar
multitud de aplicaciones que nos muestran C o Perl. las variables ni tener en cuenta su
parte de las capacidades de este lenguaje Pero veamos una breve comparativa con tamao, ya que son completamente din-
de alto nivel. Vamos a enumerar algunas otros lenguajes: micas. Adems, dispone de un gestor de
de sus sorprendentes caractersticas: Hola Mundo en C: memoria que, de manera similar al de
Orientado a objetos Esto no significa java, se encargar de liberar memoria de
que sea exclusivamente orientado a main () objetos no utilizados. Sin embargo, y al
objetos, podemos utilizarlo como quera- { igual que Java, no permite usar la memo-
mos, aunque le sacaremos ms prove- printf(Hola Mundo); ria a bajo nivel como C, con el que nos
cho si usamos su implementacin de } podamos referir a zonas de memoria
OOP (Programacin Orientada a Obje- directamente.
tos). Hola Mundo en Java: Adems se puede combinar con otros
Libre y gratuito Desde la red, pode- mltiples lenguajes de programacin.
mos descargar el interprete y su cdigo public static void U Podemos mezclar en nuestras aplicaciones
fuente, y al ser un lenguaje de script, main(String args[]) Python y Java (Jython ver el artculo al
viene con la mayora de las distros { respecto en la pgina 28 de este especial),
GNU/Linux de manera predeterminada, System.out.println(Hola U por ejemplo. O Python con C/C++, lo
siendo posible ver el cdigo de una Mundo); cual hace que resulte mas potente si cabe.
enorme parte del software desarrollado } Python tambin cuenta con una amplia
para Python. biblioteca de mdulos que, al estilo de las
Portable Al ser interpretado, podemos Hola Mundo en Python: bibliotecas en C, permiten un desarrollo
ejecutar nuestros programas en cual- rpido y eficiente.
quier S.O. y/o arquitectura simplemente print Hola Mundo La sencillez de Python tambin ayuda a
teniendo instalado previamente el intr- que los programas escritos en este lenguaje
prete en nuestro ordenador . Aunque los Hola Mundo no son muy sean muy sintticos. Como podemos ver
Potente Realizar un programa bajo indicativos de nada, ntese la ausencia de en el ejemplo Hola Mundo anterior, la
este lenguaje seguramente nos costara puntos y comas, llaves, declaracin de fun- simplicidad llega a ser asombrosa. Si este
entre la mitad o la cuarta parte del ciones y otros trastos que entorpecen el programa ya supone ahorrarte 4 5 lneas
tiempo que tardaramos en desarrollar el cdigo. Esto es incluso ms obvio en pro- de cdigo, con una sintaxis tan sencilla y
mismo programa en C/C++ o Java. gramas ms largos. ordenada podemos imaginar que un pro-
Claro Puede que sta sea una de las Python dispone de otras caractersticas grama de 1000 lneas en Java, en Python se
caractersticas ms alabadas de Python: que lo convierten en el lenguaje favorito redujeran unas 250.

6 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Primeros Pasos INTRODUCCIN

Uso forma. En Python todo se hace de un solo while(<condicin>)


Para empezar a matar el gusanillo, pode- modo, de hecho es parte de su filosofa:
mos ir haciendo algunas pruebas intere- Solo Hay Una Manera de Hacer Las al igual que if
santes. Vayamos al intrprete Python. Para Cosas.
ello, basta con escribir python en el Comencemos con lo ms simple en todo if (<condicin>)
prompt de una terminal (por ejemplo, lenguaje, la asignacin a una variable:
Bash en GNU/Linux o Powershell en Win- Por qu no he puesto cuerpos de ejemplo
dows) y probar nuestro Hola Mundo: cantidad = 166.386 en esos bucles? Pues porque ahora viene
otra novedad. En Python no se usan las
>>> print Hola Mundo (Para los que no lo recuerden, 166,386 era famosas { y } para delimitirlas. Se decidi
Hola Mundo la cantidad de pesetas que hay en un euro). (y no a todo el mundo le gusta) que se usa-
Lo primero que hay que apreciar es que ra la posicin como delimitador. Esto, as,
Ahora probemos a utilizar algunas varia- no se usa el ;. Esto es una caracterstica de suena algo extrao, pero si se ve es mucho
bles: las muchas que hacen a Python diferente. ms sencillo:
Una sentencia acaba con el retorno de
>>> suma = 15 + 16 carro, aunque el ; se puede usar cuando >>> cantidad = 2
>>> dos sentencias estn en la misma lnea: >>> for i in range(1,10):
>>> print el resultado de la U print cantidad*i
suma es: , suma cant = 166.386; ptas = 3000
el resultado de la suma es: 31 Comencemos mirando a ese :. Los dos
Como es un lenguaje dinmico, no hay puntos marcan el inicio de un bloque de
Es recomendable trastear un poco con esto que declarar el tipo de las variables, pero cdigo Python. Ese bloque aparecer en
antes de ponernos a programar algo ms una vez que una variable ha sido definida bucles, funciones o mtodos. Si nos fija-
complicado, ya que de esta manera es ms (lo que se hace asignndole un valor), esa mos bien en la sangra de la siguiente
sencillo hacerse con la sintaxis mucho ms variable guarda su tipo y no lo cambiar a lnea, vemos una serie de espacios (logra-
rpidamente viendo los resultados de cada lo largo de la ejecucin del programa. dos pulsando la tecla TABULADOR) y una
prueba. Tambin tenemos a nuestra disposicin sentencia. Esos espacios son vitales, ya
Veamos ahora alguna propiedad intere- los operadores habituales de otros lengua- que marcan la existencia de un bloque de
sante de Python. Los ficheros en Python jes: +, -, *, /, etc. Y una vez que sabemos cdigo. Adems, son obligatorios.
no tienen por qu llevar extensin nin- cmo manejar operadores y asignaciones, Este es uno de los hechos ms contro-
guna, pero seguramente querremos tener- se pueden hacer cosas tiles, pero slo de vertidos de Python, pero tambin mejora
los diferenciados del resto de ficheros que manera lineal. Para que la ejecucin no sea mucho la legibilidad del cdigo. Slo existe
tengamos. Por ello se suele utilizar la lineal necesitamos los bucles y los condi- una manera de escribir Python, as que
extensin .py. cionales. todo el cdigo Python se parece y es ms
Pero cmo sabe el sistema que intr- El tema de los bucles en Python es algo fcil de entender. El bloque acaba cuando
prete utilizar cuando queramos ejecutar especial. Puede que estemos acostumbra- desaparecen esos espacios:
nuestros scripts? Sencillo: Imaginemos que dos a los que son de tipo C:
tenemos un ejemplo.py, al ser un lenguaje >>> for i in range(1,10):
tipo script, debemos poner #! seguido de la for (a = 1; a < 10; a++) U ... print cantidad*i
ruta del intrprete de Python en la cabe- printf(%d\n,a); ... cantidad = cantidad + 1
cera del fichero. De esta manera, y dndole ...
permisos de ejecucin (chmod +x ejem- Sin embargo, en Python se toma un enfo- >>>
plo.py en GNU/Linux), obtenemos un pro- que funcional prestado de otros lenguajes
grama listo para su ejecucin. como Lisp o Haskell. Se utiliza una fun- Funciones y Objetos
Si nuestro intrprete Python se halla en cin especial llamada range. La funcin Ya tenemos las piezas fundamentales para
/usr/bin/, el contenido de ./ejemplo.py range genera una lista de nmeros: entender las funciones y los objetos. La
quedara, pues, como sigue: declaracin de una funcin en Python
range(1,10) tiene una sintaxis muy simple:
#! /usr/bin/python
print Hola Mundo generar una ristra de nmeros del 1 al 9. def nombre_funcion U
De esta manera, podemos utilizar una fun- (<lista argumentos>):
Despus de toda la introduccin tcnica va cin range para crear un bucle for en <CUERPO>
siendo hora de que veamos cmo es el for- Python de la siguiente manera:
mato de los programas en Python. Esto es Fcil no? Al igual que las variables, a los
importante porque, mientras la norma en for i in range(1,10) argumentos no se les asignan tipos. Exis-
la mayora de los lenguajes es dejar al pro- ten muchas posibilidades en los argumen-
gramador la decisin de la manera en que Este bucle iterara con i desde 1 a 9, ambos tos, pero los veremos ms tarde. De
deben ser formateados los archivos fuente, inclusive. La versin del bucle while en momento examinemos un ejemplo sim-
en Python es obligatorio hacerlo de cierta Python es ms normal ple:

W W W. L I N U X - M A G A Z I N E . E S WWW.LINUX- MAGAZINE.ES PYTHON 7


INTRODUCCIN Primeros Pasos

>>> def imprime (texto):


... print texto
Listado 2: Adicin y Eliminacin de Elementos de Lista
>>> imprime(Hola mundo) 01 >>> b = [ 1 , 2 , 1 ] 15 [2, 57, 1, 3]
Hola mundo 02 >>> b.append(3) 16 >>> b.append(1)
03 >>> b.append(4) 17 >>> b
>>>
04 >>> b 18 [2, 57, 1, 3, 1]
05 [1 , 2 , 1 , 3 , 4 ] 19 >>> b.count(1)
Vuelve a ser sencillo. Y los objetos? Pues 06 >>> b.remove(1) 20 2
tambin son bastante simples de imple- 07 >>> b 21 >>> b.index(57)
mentar. Podemos ver un ejemplo en el Lis- 08 [2, 1, 3, 4] 22 1
tado 1. Con class declaramos el nombre de 09 >>> b.pop() 23 >>> b.sort()
la clase, y los def de su interior son los 10 4 24 >>> b
mtodos. El mtodo __init__ es el cons- 11 >>> b 25 [1, 1, 2, 3, 57]
tructor, donde se asignan los valores inicia- 12 [2, 1, 3] 26 >>> b.reverse()
les a las variables. __init__ es un mtodo 13 >>> b.insert(1,57) 27 >>> b
14 >>> b 28 [57, 3, 2, 1, 1]
estndar y predefinido, lo que quiere decir
que tendremos que usar se y no otro para
inicializar el objeto. Todos los mtodos, Estructuras de Datos dad con la que Python trata las listas nos
aunque no acepten valores, poseen un Una de las razones por las que los progra- permite usarlas para multitud de tareas, lo
parmetro self. Este es otro punto contro- mas scripts de Python resultan tan poten- que simplificar mucho nuestro trabajo.
vertido en Python; self es obligatorio, pero tes, es que nos permiten manejar estructu- A pesar de su potencia, las listas no pue-
no se usa al invocar el mtodo. Cmo se ras de datos muy verstiles de manera den hacerlo todo, existiendo otra estruc-
crea el objeto? muy sencilla. En Python estas estructuras tura que rivaliza con ellas en utilidad, los
son las Listas y los Diccionarios (tambin Diccionarios. Mientras las listas nos permi-
>>> a = Objeto(20) llamados Tablas Hash). ten referenciar a un elemento usando un
Las listas nos permiten almacenar una nmero, los diccionarios nos permiten
Es como llamar a una funcin. A partir de cantidad ilimitada de elementos del mismo hacerlo con cualquier otro tipo de dato.
este momento a es una instancia de tipo. Esto es algo inherente a casi todos los Por ejemplo, con cadenas, de hecho, casi
Objeto, y podemos utilizar sus mtodos: programas, as que Python las incorpora siempre con cadenas, de ah que su nom-
de fbrica. Las listas de Python tambin bre sea diccionario (vase el Listado 3).
>>> print a.getCantidad() vienen provistas de muchas ms opciones Las listas y los diccionarios se pueden
20 que sus semejantes en otros lenguajes. Por mezclar: diccionarios de listas, listas de
>>> a.setCantidad(12) ejemplo, vamos a definir una lista que diccionarios, diccionarios de listas de dic-
>>> print a.getCantidad() guarde una serie de palabras: cionarios, etc. Ambas estructuras combi-
12 nadas poseen una enorme potencia.
>>> a = [Hola, Adios, U
No hay que preocuparse por la administra- Buenas Tardes] Algoritmos + Estructuras de Datos
cin de la memoria del objeto ya que, >>> a = Programas
cuando a no apunte al objeto, el gestor de [Hola, Adios, U Ahora nos toca poner todo esto en prc-
memoria liberar su memoria. Buenas Tardes] tica. Lo normal es hacer un programa sen-
Ya tenemos las bases para construir algo cillo. Pero en lugar de eso vamos a imple-
interesante. Por supuesto, nos dejamos Python indexa comenzando desde 0, de mentar algo que sea creativo. Este pro-
infinidad de cosas en el tintero, pero siem- manera que Hola es el elemento 0, Adios grama es el que se usa en el libro La prc-
pre es mejor comenzar con un pequeo el 1 y Buenas Tardes el 2, y la longitud de tica de la programacin de Pike y Kernig-
conjunto de herramientas para empezar a la lista es 3. Podemos comprobarlo de esta han para ilustrar cmo un buen diseo
usarlas. En todo caso, tendremos tiempo forma: sobrepasa al lenguaje que usemos para eje-
de profundizar en los siguientes artculos
de este especial. >>> a[1] Listado 3: Ejemplo Diccionario
Adios
01 >>> dic = {}
Listado 1: Una Clase Sencilla >>> len(a)
02 >>> dic[Perro] = hace guau
3 guau
01 class Objeto:
03 >>> dic[Gato] = hace miau
02 def __init__ (self, cantidad): miau
Es posible aadir elementos a las listas de
03 self.cantidad = cantidad 04 >>> dic[Pollito] = hace pio
04 varias maneras. Si miramos el Listado 2, pio
05 def getCantidad(self): veremos la ms sencilla. Las listas tambin 05 >>> dic
06 return self.cantidad se pueden comportar como una Pila, con 06 {Perro: hace guau guau,
07 las operaciones append y pop. Con insert 07 Gato: hace miau miau,
08 def setCantidad(self, introducimos un elemento en la posicin 08 Pollito: hace pio pio}
cantidad): 09 >>> dic[Perro]
especificada (recuerda que siempre
09 self.cantidad = cantidad 10 hace guau guau
comenzamos a contar desde 0). La facili-

8 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Primeros Pasos INTRODUCCIN

cutarlo. En el libro se implementa el diseo Para hacer las pruebas es recomendable Veamos cmo funciona. Lo primero es ir
en C, C++, Java, Perl y AWK. Nosotros lo conseguir un texto de gran tamao. En tex- introduciendo en el diccionario dos prefi-
haremos en Python (ver Listado 4). tos pequeos no surtir tanto efecto. En el jos como ndice y las palabras que les
El programa acepta un texto como proyecto Gtenberg podemos conseguir siguen en el texto dentro de una lista refe-
entrada y genera un texto como salida, pero infinidad de textos clsicos de enorme renciada por ellos. Eso es un diccionario
este segundo texto no tiene sentido. Lo que tamao en ASCII. Pero somos conocedores con dos palabras como ndice que contiene
queremos hacer es generar texto sin sentido de que no todo el mundo entiende el una lista:
pero con estructuras que s lo tengan. Puede idioma anglosajn, as que en lugar de ir al
parecer algo muy complicado, pero no lo es proyecto Gtenberg, podemos coger cual- DICCIONARIO[ palabra 1, U
tanto si usamos la tcnica de cadenas de quier texto que queramos modificar, por palabra 2] -> U
Markov. La idea es coger 2 palabras, elegir ejemplo, alguna noticia de poltica de un LISTA[palabra,...]
una palabra que suceda a cualquiera de las diario digital o alguna parrafada de algn
dos y reemplazar la primera por la segunda blog. O sea, si tenemos las palabras La gente
y la segunda por la palabra escogida. De Este programa es interesante porque per- est La gente opina, crearemos un dic-
esta manera vamos generando un texto que, mitir utilizar las estructuras de datos que cionario de la siguiente forma:
aunque carece de sentido, normalmente se Python implementa, en particular en los
corresponde con la estructura de un texto diccionarios, que generan una tabla donde >>> dict[La, gente] = U
normal aunque disparatado. los ndices sern cadenas de texto. [est]
>>> dict[La, gente].U
Listado 4: markov.py Genera un Texto No-Tan-Aleatorio append(opina)
>>> dict[La, gente]
01 #!/usr/local/bin/python 22 # Fin de archivo
[est,opina]
02 23 dict.setdefault((w1, w2), []
03 #Importamos dos mdulos ).append(no_palabra)
04 #random [que hace] 24 Vamos haciendo esto de manera sucesiva
05 #y sys [que hace] 25 # GENERAMOS LA SALIDA con todos los conjuntos de dos palabras, y
06 import random 26 w1 = no_palabra obtendremos un diccionario en el que
07 import sys 27 w2 = no_palabra muchas entradas referenciarn a una lista
08 28 de ms de un elemento.
09 no_palabra = \n 29 # puedes modificarlo
La magia aparece cuando generamos el
10 w1 = no_palabra 30 max_palabras = 10000
texto, puesto que lo que hacemos es
11 w2 = no_palabra 31
32 for i in xrange(max_palabras):
comenzar por las dos primeras palabras, y
12
33 nueva_palabra = cuando existan varias posibilidades para
13 # GENERAMOS EL DICCIONARIO
random.choice(dict[(w1, w2)]) esa combinacin (como con el ejemplo de
14 dict = {}
34 La,gente), escogeremos aleatoriamente
15
35 if nueva_palabra == no_palabra: entre ellas. Imaginemos que escogemos
16 for linea in sys.stdin:
36 sys.exit()
17 for palabra in linea.split(): opina, entonces escribimos opina por la
37
18 dict.setdefault( (w1, w2), [] pantalla y buscamos en gente, opina y
).append(palabra) 38 print nueva_palabra;
39
as sucesivamente, hasta llegar a no_pala-
19 w1 = w2
40 w1 = w2 bra.
20 w2 = palabra
41 w2 = nueva_palabra Para entender mejor el funcionamiento
21
del programa recomendamos copiar el
cdigo fuente y pasarle unos ejemplos
Listado 5: Salida de markov.py (pongamos, con cat texto.txt |
./markov.py) y ver los resultados. En el
Para empezar, debemos saber por qu Python es obligatorio hacerlo de una
manera. Como es un lenguaje tipo script, debemos poner #! seguido de la Listado 4 vemos un ejemplo de la salida
ejecucin del programa. Tambin tenemos a nuestra disposicin los operadores utilizando el texto de este artculo. El texto
habituales de otros lenguajes: +, -, *, /, etc. Y una vez que sabemos cmo generado casi tiene sentido, pero no del
manejar operadores y asignaciones, se pueden mezclar: diccionarios de listas
de diccionarios, diccionarios de listas, listas de Python tambin vienen todo.
provistas de muchas ms opciones que sus semejantes en otros lenguajes. Por Despus podemos intentar cambiar
ejemplo, vamos a definir una lista que guarde una serie de espacios (logrados cosas en el programa, por ejemplo, en
pulsando la tecla TABULADOR) y una sentencia. Esos espacios son vitales, ya
que marcan la existencia de un texto que, aunque carece de sentido, lugar de utilizar 2 palabras como ndice del
normalmente se corresponde con la que Python trata las listas de diccionario, podemos probar con 1 o con
diccionarios, diccionarios de listas, listas de Python resultan tan 3, y tambin con el tamao del texto que
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 se le pase. Se pueden conseguir cosas muy
obligatorio hacerlo de una manera. Como es un diccionario con dos palabras interesantes.
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 Recursos
la memoria a bajo nivel como C con el tamao del texto que se usa en el Listado
1. [1] Python: http://www.python.org

W W W. L I N U X - M A G A Z I N E . E S PYTHON 9
INTRODUCCIN Ficheros

Manejo bsico de ficheros

lbum

Ruslan Olinchuk - 123RF.com


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 mos hacer es llamar a reset_variable() y Para acceder a un fichero, primero
sobre cmo trabajar con objetos en as asegurarnos de que nuestro valor sea necesitamos crear un objeto file. El
Python. Fue muy simple, pero ya nos asignado, pero entonces destruiramos el objeto file es parte de la librera base de
daba la posibilidad de organizar nuestro valor anterior y no sabramos qu podra Python, as que no es necesario importar
cdigo en torno a ellos. Python hace un pasar. ninguna librera.
uso extensivo de los objetos en sus APIs, Por lo tanto, necesitamos un meca-
y especialmente del control de errores nismo de comunicacin para darle a >>> archivo = file(texto.txt)
mediante excepciones, lo que nos da la conocer al usuario que esa variable ya
opcin de lanzarlas cuando algo va mal. est asignada. Esto lo podemos hacer Por definicin, file abre los ficheros en
Una excepcin es un mensaje que pode- con las excepciones. modo de slo lectura. Eso significa que si
mos capturar cuando se ejecuta cierta En el Listado 2 aparece una clase que el fichero no existe, obtendremos un
funcin o mtodo y aparece un error de hereda de la clase Exception llamada error. Para verificar si el fichero existe
algn tipo. Normalmente controlamos Var_Asignada. Cuando en la clase podemos usar la funcin exists() de la
estos errores mediante el valor devuelto obj_variable intentamos asignar un valor librera os.path.
por la funcin (como por ejemplo en C). a la variable var y sta no es 0, entonces
Esta tcnica es engorrosa, pero al igual se dispara, se eleva, la excepcin >>> import os.path >>>
que todo, tiene sus virtudes y sus des- Var_Asignada. Si no controlamos la por- os.path.exists(texto.txt) True
ventajas. Pero Python hace uso de las cin de cdigo en la que se encuentra >>> os.path.extsts(algo-
excepciones en su lugar. set_variable() y aparece una excepcin, peludo-y-feo.txt) False
Cuando una funcin genera una el programa se detendr y acabar.
excepcin, decimos que eleva una excep- La idea detrs de las excepciones es Por lo tanto, si vamos a abrir un
cin. Es muy normal tener que controlar que es posible tratarlas y evitar males fichero, podemos asegurarnos de que ya
las excepciones en las operaciones que mayores, pudiendo en ocasiones incluso existe.
realicemos con recursos que pueden no recuperarnos de ellas. Para ello est la Si en lugar de leerlo lo que queremos
estar disponibles. Por eso las vamos a estructura tryexcept, con la cual rodea- es crearlo, deberemos invocar al cons-
ver, puesto que aqu vamos a trabajar mos el cdigo que puede disparar excep- tructor de file con los parmetros:
con archivos y conexiones a Internet. ciones (Vase el Listado 3).
Crearemos un objeto que gestione un A partir de ahora, y hasta que no expli- >>> archivo = file(texto.txt,U
recurso que puede no estar disponible. quemos con ms profundidad el tema de w)
En este caso el objeto gestiona una varia- las excepciones, cuando digamos que
ble (vase el Listado 1). una funcin genera una excepcin, signi- Este segundo parmetro opcional nos
Alguien puede crear un objeto de la ficar que ese cdigo deber estar rode- permite definir el tipo de acceso que
clase obj_variable y llamar al mtodo ado con una estructura tryexcept. vamos a realizar al fichero. Tenemos
set_variable(23), pero cmo puede estar varias posibilidades: podemos leer (r),
seguro de que la variable var tiene el Trabajo con Ficheros escribir (w), aadir al final del fichero
valor 23 despus de la llamada? Puede Ya que hemos conseguido cierta soltura (a) y tambin tenemos el acceso de lec-
que var no tuviese el valor inicial de 0, con los conceptos de objetos en Python, tura/escritura (r+w). Disponemos tam-
porque otra llamada anterior ya podra ahora vamos a ver cmo se manejan los bin del modificador b para indicar
haberla asignado. Lo nico que podra- accesos a ficheros en l. acceso binario. Por defecto, Python con-

10 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Ficheros INTRODUCCIN

sidera todos los ficheros de texto. Vemos la cadena hasta el EOF. En cambio, si y cuando la funcin fuese a borrar el
todas las combinaciones en el Listado 4. slo leemos el EOF, entonces obtenemos directorio dos, se encontrara con que
Si todo ha ido bien, con cualquiera de una null. Esto es importante para com- no puede porque existe dentro de l un
estas llamadas tendramos en archivo un probar que hemos acabado con el directorio llamado cuatro y parara.
objeto que gestiona el archivo indicado. fichero. As, un bucle que escriba por Imaginemos ahora que necesitamos
Ahora podemos operar sobre l. pantalla el contenido del fichero compro- cambiar el directorio en el que estamos
Las operaciones ms tpicas son las de bara en cada vuelta si la cadena que trabajando. En el momento de arrancar
leer desde el archivo y escribir en l. Para devuelve readline() es null. el programa, el llamado directorio de
ello, el objeto file dispone de los mtodos Ahora que ya sabemos crear archivos, trabajo es decir, el directorio donde de
read(), readline(), write() y writeline(). tenemos que aprender a borrarlos. Esto manera predeterminada se realizarn
Todos ellos operan con cadenas de carac- se realiza mediante la funcin remove() todos los cambios es el directorio que
teres: readline() y writeline() trabajan de la librera os. Esta funcin acepta la alberga el programa o bien el directorio
con lneas de texto (acabadas en retorno ruta de un fichero y lo borra. Si en lugar desde el que se ejecut. Pero, claro, no
de carro), mientras que read() y write() de un fichero le pasamos un directorio siempre querremos que el programa uti-
lo hacen con cadenas sin restricciones. elevar una excepcin OSError. lice ese directorio.
Lo que vemos en el Listado 5 son Hay que tener en cuenta que, a no ser
algunas manipulaciones sobre un >>> import os que utilicemos rutas absolutas, cualquier
fichero. Lo primero que tenemos que >>> os.remove (texto.txt) referencia a un fichero se tomar con
hacer es crear el fichero, para lo cual lo >>> relacin al directorio de trabajo inicial.
abrimos en modo de escritura, w, que lo Para poder cambiar el directorio de tra-
crear o truncar el existente (lo borrar Directorios y Sistema de Ficheros bajo, el mdulo os tiene la funcin
para crearlo de nuevo. Si lo hubiramos Con estos pocos mtodos tenemos ya a chdir(). Si lo invocamos dentro de nues-
querido aadir al final, habramos usado nuestro alcance la manipulacin bsica tro programa:
a). Posteriormente escribimos en l una de ficheros. Pero vamos a necesitar para
cadena con un retorno de carro en mitad nuestro programa la posibilidad de crear >>> os.chdir(/tmp)
(para hacer nuestras pruebas) y cerra- directorios. Cmo lo haremos? Pues
mos el fichero. Es importante cerrar los mediante la funcin mkdir(), que acepta Desde ese momento, cualquier referencia
ficheros cuando dejemos de usarlos, una cadena y crea un directorio con ese a un fichero ser direccionada a /tmp.
pero en este caso la razn para cerrarlo nombre. Si queremos crear un directorio Ahora podemos:
es que vamos a volver a abrirlo en modo que est dentro de otros directorios tam- abrir, cerrar, modificar ficheros
de lectura. bin nuevos tenemos que usar make- crear, eliminar un directorio
Ahora volvemos a abrir el fichero en dirs(). Ambas funciones pertenecen al cambiar el directorio de trabajo
modo de lectura, y leemos 4 bytes que mdulo os, por lo que para usarlas ten- Vamos a ir un poco ms all.
almacenamos en la variable cadena. dremos que hacer:
Cuando leemos con read(), avanzamos Llamadas a Otros Programas
en el fichero, siendo esta la razn de que >>> import os A veces es ms sencillo usar una utilidad
readline() que viene a continuacin lea >>> os.mkdir(uno) del sistema operativo que crearla noso-
la cadena mundo\n en lugar de Hola >>> os.makedirs(dos/tres)
mundo. Tambin vemos que se para en Listado 2: Uso de Excepciones
el retorno de carro en lugar de continuar. Para borrar esos directorios usaremos las
01 class Var_Asignada(Exception):
El segundo readline() ya nos permite funciones rmdir() y removedirs(). La pri-
02 Excepcin que se dispara al
leer la cadena Adis mundo. mera borra un directorio, mientras que intentar asignar una variable ya
Pero qu ocurrira si en una de las la segunda borra una ruta de directorios. asignada en obj_variable
03 pass
lecturas nos encontrsemos con el fin de Vamos a ver esto con ms detenimiento.
04
fichero? En el caso de que leysemos una 05 class obj_variable:
cadena con el fin de fichero (EOF), al >>> os.rmdir(uno) 06 Administra una variable
final simplemente nos quedaramos con >>> os.removedirs(dos/tres) 07 def __init__(self):
08 self.var = 0
09
Listado 1: Una Clase Python rmdir() borrar el directorio uno, que
10 def set_variable(self, valor):
no contiene ningn otro objeto en su
01 class obj_variable: 11 if (self.var == 0):
interior (ni directorios, ni ficheros). En 12 self.var = valor
02 __init__(this):
03 var = 0 caso de tenerlo, la llamada devolvera un 13 else:
04 error. La funcin removedirs() comenza- 14 raise Var_Asignada
05 set_variable(this, valor): ra a borrar desde el directorio que est 15 def reset_variable(self):
06 if (var == 0): ms a la derecha de la ruta (tres) hacia 16 self.var = 0
07 var = valor el que est ms a la izquierda (dos). 17
08 18 a = obj_variable()
Pero imaginemos que dentro de dos
09 reset_variable(this): 19 a.set_variable(12)
10 var = 0 tambin hay un directorio cuatro. 20 a.set_variable(34)
Entonces se borrara el directorio tres,

W W W. L I N U X - M A G A Z I N E . E S WWW.LINUX- MAGAZINE.ES PYTHON 11


INTRODUCCIN Ficheros

En este apartado vamos a comenzar


Listado 3: Ms Excepciones con lo bsico. Queremos traer un recurso
Listado 4: Acceso a Ficheros
01 >>> try: de la red a nuestra mquina, y para ello 01 archivo = file(texto.txt,r)
02 ... set_variable(12) emplearemos una URL del estilo http:// 02 archivo = file(texto.txt,w)
03 ... set_variable(34) www.algunaweb.algo/imagen.jpg. Pero 03 archivo = file(texto.txt,a)
04 ... except: 04 archivo =
primero necesitamos crear una conexin file(texto.txt,r+w)
05 ... print ERROR: Se ha intentado
asignar con el servidor. 05 archivo =
06 ... print un valor a una Para ello vamos a utilizar la librera file(texto.txt,r+b)
variable ya asignada 06 archivo = file(texto.txt,rb)
httplib que viene de serie con Python.
07 ...
08 ERROR: Se ha intentado asignar Esta librera nos permite establecer una
09 un valor a una variable ya conexin con un servidor http y man- programas. En UNIX es algo comn.
asignada darle comandos. Los comandos http Pero cmo podemos obtener los par-
10 >>>
son simples, y de todos ellos slo nos metros de ejecucin en Python? De
interesa uno, el comando GET. Cuando nuevo tenemos que recurrir a una libre-
tros, como por ejemplo, un procesado accedemos a un servidor http, por ra: la librera sys.
usando tuberas en UNIX. Puede que ejemplo para ver una pgina web, lo sys nos proporciona el acceso a los
simplemente tenga que acceder a alguna que hacemos es pedirle objetos. Esto se argumentos a travs de su variable argv.
informacin como la que nos da uname. hace mediante el comando GET Esta variable es en realidad una lista, por
El caso es que siempre es importante <objeto>. Por ejemplo, si queremos la lo que podemos obtener los argumentos
tener la posibilidad de ejecutar otros pro- pgina index.html de la web http:// accediendo a las posiciones de la misma.
gramas desde nuestro programa Python. www.python.org, primero conectamos La posicin 0 contiene el nombre del
Para ello usamos la funcin system del con el servidor http y despus, una vez programa que estamos ejecutando y, a
mdulo os. Por ejemplo: conectados, le enviamos el comando partir de la posicin 1, encontraremos
GET index.html. En ese momento el los parmetros pasados. Al ser una lista,
>>> import os servidor nos devuelve por el mismo podemos conocer la cantidad de parme-
>>> os.system (uname -a) canal el contenido del archivo tros llamando a len().
Linux rachel 3.1.2-1.fc16.x86_64 index.html.
#1 SMP Tue Nov 22 09:00:57 UTC 2011 Dicho as parece muy fcil, pero es Programa
x86_64 x86_64 x86_64 GNU/Linux una tarea que en un lenguaje de ms Ahora es el momento de poner todo lo
0 bajo nivel requerira gran cantidad de aprendido en prctica con un programa
>>> libreras y control de errores. que puede ser til. En este caso vamos a
Lo primero es importar la librera crear uno que realizar las siguientes
El parmetro que le pasamos a system es httplib. Creamos entonces una conexin tareas:
una cadena con la instruccin Bash (en con el host en cuestin y pedimos el El programa aceptar un parmetro de
este caso) y sus switches y flags. system archivo index.html. Esa conexin genera entrada que le indicar el nombre de
nos devuelve la salida de la instruccin ( una respuesta. La respuesta est for- un fichero.
Linux rachel 3.1.2-1.fc16.x86_64 #1 SMP mada por varias partes, entre ellas un El programa abrir ese fichero y lo
Tue Nov 22 09:00:57 UTC 2011 x86_64 cdigo numrico (como el famoso 404), leer lnea por lnea. Cada lnea del
x86_64 x86_64 GNU/Linux) y el estado de un texto que describe el error y una fichero ser la direccin URL de una
salida resultante de la ejecucin de la ins- conexin al archivo que pedimos. En el imagen.
truccin (0 recurdese que 0 indica que caso de una conexin correcta recibire- Cada URL ser introducida dentro de
la instruccin ha acabado sin errores). mos un 200, un OK y una conexin con una lista para su uso posterior.
el fichero. De esa conexin lee-
Python y la Web mos con read() el contenido y Listado 5: Lectura y Escritura de Ficheros
Python posee gran cantidad de libreras lo almacenamos en una varia-
01 >>> archivo = file(/tmp/texto.txt,w)
para trabajar con recursos de Internet. ble que llamamos dato. Enton-
02 >>> archivo.write(Hola mundo\nAdios
De hecho, Django [1] , un servidor de ces podremos cerrar la cone- mundo)
aplicaciones con gran xito, est creado xin como si de un fichero se 03 >>> archivo.close()
en Python y hace uso de todas sus carac- tratara. 04 >>>
tersticas. Mailman [2] o Bittorrent [3] En ese momento ya tenemos 05 >>> archivo = file(/tmp/texto.txt,r)
06 >>> cadena = archivo.read(4)
son tambin buenos ejemplos. la informacin que queramos
07 >>> cadena
Debido a su flexibilidad, Python es en dato y el canal cerrado. No 08 Hola
usado como lenguaje de implementacin es muy difcil, no? Veremos un 09 >>> cadena = archivo.readline()
para multitud de aplicaciones de red as ejemplo en el programa final de 10 >>> cadena
como aplicaciones distribuidas. Por eso, este artculo. 11 mundo\n
no es de extraar que Python suela ser el 12 >>> cadena = archivo.readline()
Paso de Parmetros 13 >>> print cadena
lenguaje en el que se implementan
14 Adios mundo
muchas de las ms novedosas tecnolo- Estamos acostumbrados a 15 >>> archivo.close()
gas de red. poder pasar parmetros a los

12 PYTHON 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 064 os.makedirs(componentes[0])
002 065 os.chdir(componentes[0])
003 # ---NOTA-------------------------------------- 066 print Creando directorio + componentes[0]
004 # El fichero que debe ser pasado como argumento 067
005 # debe consistir en un listado con una url por 068 def descarga_urls(lista):
006 # lnea. 069 # Recorre la lista de urls usando el objeto
007 # --------------------------------------------- 070 # Lista_URLs, las descarga y despus las
008 071 # guarda en ficheros con el mismo nombre que
009 class Lista_URLs: 072 # el de la imagen.
010 Recibe un fichero y carga sus cadenas en una 073
lista. Provee de mtodos para obtener de nuevo las 074 lista.rebobina()
cadenas desde la lista.
075
011
076 while( not lista.fin() ):
012 def __init__(self,nombre):
077 url = lista.siguiente()
013 # La lista donde guardaremos las URLs
078
014 self.lista= []
079 # dividimos la url en dos partes
015 # El contador que usaremos para comprobaciones
080 # lo que descargamos y la url http
016 self.contador = 0
081
017
082 # Componentes es una lista que contiene
018 # pasamos el nombre del fichero menos el ltimo 083 # las cadenas resultantes de trocear la
carcter
084 # cadena de texto de la URL usando /
019 self.archivo = file(nombre)
085 # como separador. Por ejemplo:
020 self.cadena = self.archivo.readline()
086 # http://www.python.org/index.html
021
087 # componentes = [http:, , www.python.org,
022 while(self.cadena != \n):
088 # index.html]
023 #Metemos la cadena en la lista
089 componentes = url.split(/)
024 self.lista.append(self.cadena)
090 servidor = componentes[2]
025 self.cadena = self.archivo.readline()
091
026 self.archivo.close()
092 # Construimos la ruta de la imagen, que
027
093 # consiste en toda la ruta si eliminamos
028
094 # al servidor y a http://
029 def rebobina(self):
095 ruta_imagen = /
030 # Hace que se comience de nuevo
096 for i in range( 3, len(componentes)):
031 # por el principio en la lista.
097 ruta_imagen = ruta_imagen + / + componentes[i]
032 self.contador = 0
098
033
099 # Descarga el fichero y lo guarda con el nombre.
034
100 # El nombre se saca de la URL.
035 def siguiente(self):
101 # url[:-1] es la cadena url menos el ltimo carcter.
036 # Devuelve el siguiente elemento o
102 print Descargando imagen: + url[:-1]
037 # en caso de llegar al final.
103 conexion = httplib.HTTPConnection(servidor)
038 if ( self.contador >= len(self.lista)):
104 conexion.request(GET, ruta_imagen)
039 return
105 respuesta = conexion.getresponse()
040 else:
106 # datos contiene ahora la imagen y la guardamos
041 self.valor = self.lista[self.contador]
107 datos = respuesta.read()
042 self.contador = self.contador + 1
108 conexion.close()
043 return self.valor
109
044
110 # el nombre del fichero es el ltimo elemento
045 def fin(self):
111 # de la lista componentes
046 # Comprueba que hemos llegado al final
112 nomb_fichero = componentes[len(componentes) -1]
047 # de la lista. Preguntamos si hemos llegado
113 # eliminamos el \n final
048 # al final antes de avanzar.
114 nomb_fichero = nomb_fichero[:-1]
049 return (self.contador == len(self.lista))
115
050
116 # Abrimos el fichero, escribimos y cerramos
051 def crea_directorio(cadena):
117 archivo = file(nomb_fichero ,w)
052 # Comprueba si el directorio especificado por
118 archivo.write(datos)
053 # cadena existe, en caso contrario lo crea
119 archivo.close()
054 # y cambia el directorio de trabajo
120
055 # al directorio creado.
121 def genera_index(lista):
056
122
057 componentes = cadena.split(.)
058 123 # Crea un fichero index.html.
059 if(os.path.exists(componentes[0])): 124 # Genera la cabecera, recorre la lista de URLS
060 print Error: el directorio ya existe 125 # y por ltimo escribe el pie.
061 sys.exit() 126 # Es posible mejorarlo introduciendo separadores
062 else: 127 # o ttulos entre las imgenes ;)
063 # Creamos el directorio 128

W W W. L I N U X - M A G A Z I N E . E S PYTHON 13
INTRODUCCIN Ficheros

Listado 6: Agarrafotos.py (Cont.)


129 print Generando ndice index.html 168 # Main
130 169 #------------------------------------------------
131 archivo = file(index.html,w) 170
132 171 # Esta es la tcnica estndar para organizar el
133 # Cabecera 172 # cdigo en Python, se usa la siguiente construccin
134 archivo.write(<html>\n) 173 # como punto de arranque.
135 archivo.write(<head>\n) 174
136 archivo.write(<title> Imagenes </title>\n) 175 if __name__ == __main__:
137 archivo.write(</head>\n) 176
138 archivo.write(<body>\n) 177 import httplib
139 archivo.write(<h1>Imagenes</h1>\n) 178 import os
140 archivo.write(<ul>\n) 179 import os.path
141 180 import sys
142 # siempre antes de recorrer: 181
143 lista.rebobina() 182 # Comprobamos los argumentos...
144 url = lista.siguiente() 183
145 184 if len(sys.argv) == 2:
146 # Dividimos la URL para poder utilizar 185 #Pasamos el fichero al constructor
147 # partes de ella. 186 lista = Lista_URLs(sys.argv[1])
148 componentes = url.split(/) 187
149 imagen = componentes[len(componentes) - 1] 188
150 189 crea_directorio(sys.argv[1])
151 # Recorremos las urls 190
152 while( url != ): 191 descarga_urls(lista)
153 # Imagen en HTML 192
154 archivo.write(<li><img src=\+ imagen 193 genera_index(lista)
+\></img></li>\n) 194
155 url = lista.siguiente() 195 elif len(sys.argv) == 0:
156 componentes = url.split(/) 196 # Vaya, han ejecutado sin poner argumentos...
157 imagen = componentes[len(componentes) - 1] 197 # les recordaremos como va esto ;)
158 198 print La sintaxis del programa es:\n
159 # ... y por ltimo el pie. 199 print sys.argv[0] + archivo\n
160 200 print El archivo debe contener una URL por lnea
161 archivo.write(</ul>\n) 201
162 archivo.write(</body>\n) 202 else:
163 archivo.write(</html>\n) 203 # Alguien se ha quedado corto y se ha pasado
164 204 # con el nmero de argumentos.
165 archivo.close() 205 print ERROR: la sintaxis es + sys.argv[0] +
166 <fichero>
167 #------------------------------------------------

Una vez que hayamos acabado de leer Leer las URLs. con un objeto. Es en este momento
el fichero, lo cerraremos y entraremos Crear Directorio y cambiar el directo- cuando se deja al lector que explore la
en la segunda parte del programa. rio de trabajo. posibilidad de sustituir el objeto por una
Crearemos un directorio con el nom- Descargar las URLs. variable global y las funciones de lista.
bre del archivo que nos hayan dado. Generar el archivo HTML. Este programa es muy simple, pero de
Cambiaremos el directorio de trabajo a Seguiremos estos puntos para crear las nuevo retamos a los lectores a mejorarlo
ese directorio. funciones. Las URLs las almacenaremos y a introducirle, por ejemplo, control de
Descargaremos cada una de las URLs en una lista. Deberamos usar objetos? excepciones.
dentro del directorio. Esta es una de las cosas maravillosas que Suerte.
Generaremos un archivo index.html ofrece Python: NO estamos obligados a
que muestre las imgenes. usar objetos. Y no digo que los objetos Recursos
Mucho trabajo? Para eso estn los pro- sean malos, sino que en ocasiones pue-
[1] La infraestructura Django para aplica-
gramas. Evidentemente no realizaremos den llegar a ser engorrosos. Por ejemplo, ciones web:
todas las comprobaciones que seran podramos crear un objeto Lista_URLs https://www.djangoproject.com/
necesarias, ya que en tal caso el pro- que aceptase como parmetro en su
[2] El programa administrador de listas
grama se alargara demasiado, por lo que constructor el nombre de un fichero y de correo Mailman: http://www.gnu.
se deja al lector la opcin de incluir que despus nos permitiese ir cogiendo org/software/mailman/
mejoras. Pensemos ahora en su diseo. las URLs una detrs de otra. Tambin
[3] El programar para administracin de
Tenemos varias partes: podemos hacer lo mismo usando una Torrente BitTorrent:
Comprobar y almacenar la opcin con funcin que cargue las URLs en una http://bittorrent.com/
el nombre del archivo. variable global. Aqu vamos a hacerlo

14 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Debugging INTRODUCCIN

Eliminacin de bugs de programas Python

Sebastian Kaulitzki - 123RF.com


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 pesada la programacin en otros lengua- programas Python, y en particular a la
todo el mito que rodea a los programado- jes como por ejemplo C. Pero no todo el que viene de serie con Python: el PDB,
res, hasta el mejor de ellos comete erro- monte es organo. Tambin hay una Python DeBugger (podemos ver la docu-
res diariamente. En muchas ocasiones es parte negativa: Python no es un lenguaje mentacin de PDB en el Recurso [3]).
mucho ms complicado eliminar un BUG demasiado estricto. Podemos hacer lo
que crear el propio programa. Cuenta le que queramos con las variables sin que Un Bug, Dos Bugs, Tres Bugs
leyenda que el nombre de BUG viene de el intrprete se queje hasta el ltimo Los lenguajes dinmicos, como deca-
la misma palabra que en ingls significa momento. Esta caracterstica impide la mos antes, tienen sus propias virtudes y
bicho. Dicen que los primeros ordenado- posibilidad de verificar automticamente desventajas. Los programadores ms
res eran grandes mquinas que genera- todo el cdigo en el momento en que es duros suelen decir que programar con
ban gran cantidad de calor, por lo que compilado. Un cdigo totalmente err- lenguajes dinmicos es como jugar con
innumerables insectos y otras alimaas neo, en el que por ejemplo se suman juguetes: no hay bordes cortantes con
se introducan en ellos. De vez en letras y nmeros, puede pasar desaperci- los que cortarse ni partes pequeas con
cuando, alguno tocaba dos cables y que- bido en nuestro programa hasta el da las que atragantarnos. Vamos, que son
daba frito, provocando un fallo en el sis- que se ejecuta y genera un error que poco menos que una versin infantil de
tema. Actualmente se conoce como BUG dejar al usuario con la boca abierta, y los lenguajes serios. Lo cierto es que
a todo error o situacin no controlada ciertas dudas sobre nuestra vala como hay mucha gente que no entra en estos
que impida a un programa realizar tu programadores. debates. Yo, por lo menos, prefiero hacer
tarea con normalidad. Lo cierto es que Casi a la vez que surgieron los lengua- un programa tan rpido como sea posi-
estamos bastante acostumbrados a que jes de programacin aparecieron unos ble y espero que funcione casi a la pri-
los BUGS sean parte de nuestra vida. programas que han ido unidos a ellos: mera.
Ventanas que no se cierran, programas los debuggers. Exiten muchos debuggers De dnde salen los BUGS? Lo ms
que consumen todo el espacio en memo- diferentes. GNU desarroll DDD, pero probable es que haya siempre una varia-
ria o videojuegos que se quedan blo- hace tiempo que no se ve actividad en ble implicada. La explicacin es simple:
queados. este proyecto (ver Recurso [1]). Valgrind las variables son la nica parte del pro-
Python es un lenguaje dinmico, como ha conseguido mucha fama en proyectos grama que realmente no controlamos.
muchos otros. La principal ventaja es que emplean C++ ( ver Recurso [2]). Mientras el resto del cdigo hace exacta-
que nos permite programar a alto nivel, En este artculo vamos a echar un vis- mente lo que le indicamos que haga, por
desentendindonos de toda la gestin de tazo a las herramientas que podemos ejemplo abrir un fichero, en las variables
recursos a bajo nivel que hace tan usar para localizar los fallos en nuestros suceden todo tipo de cosas mientras el

W W W. L I N U X - M A G A Z I N E . E S PYTHON 15
INTRODUCCIN Debugging

programa est en ejecucin. El problema ERROR! El intrprete de Python nos


es que no vemos esas variables mientras advierte de que algo no marcha bien:
el programa est funcionando, as que hay un error de tipo, no es posible
tenemos que imaginarnos qu est sumar un nmero entero y una instancia
pasando. de un objeto tal como hemos definido la
En condiciones ideales se puede per- clase. Desagradable verdad? Por lo
der el tiempo tratando de localizar los menos este tipo de BUG, que es tan fcil
fallos a ojo de buen cubero, pero bajo de encontrar que el propio intrprete de
estrs y con plazos, toda ayuda es poca. Python lo encuentra. Qu ocurra si el
Python adems nos permite almacenar programa no fallase, sino que no nos Figura 1: A quin dejaremos sin vacaciones?
cualquier valor dentro de una variable. diese el resultado esperado? Y si el pro-
Las variables en los lenguajes dinmicos grama le dijese a un cliente que su edad vacaciones que han escogido una serie de
como Python son casi mgicas. En ellas actual es -323134.32 aos? Este es sin empleados de una empresa. Como no
podemos almacenar un nmero: duda el peor tipo de BUG existente: el queremos que el cdigo sea largo ni
semntico. demasiado complicado, lo hemos redu-
>>> mivariable = 32 El intrprete de Python es perfecto cido al mnimo. La funcin tomar una
localizando errores sintcticos, aqullos lista de tuplas, que representa las vaca-
Y punto seguido almacenar un objeto: que tienen que ver con las propias pala- ciones de una persona en un mes. Cada
bas que escribimos. Si hacemos referen- tupla representa un periodo de vacacio-
>>> class Persona: cia a una variable que no est definida, nes, con una fecha de inicio y una fecha
... def __init__(this,nombre): Python trata de buscar su valor, ve que de fin. La idea es muy simple, aceptamos
... this.nombre = nombre no existe la variable y se queja. esa lista y despus devolvemos una
... Los errores semnticos son harina de cadena donde los das de trabajo se
>>> mivariable = PersonaU otro costal, porque se refieren a fallos que representan por un espacio, y los de
(Carlos) aparecen debido a que no se entiende lo vacaciones con un #. No es muy com-
que est pasando. Un ejemplo simple es plicado verdad?
Y sigue siendo la misma mivariable, pero el que hemos visto antes, cuando hemos As que nos ponemos manos a la obra,
de alguna manera su naturaleza ha cam- tratado de sumar una variable con una es algo sencillo, no nos llevar ni 10
biado. Ahora imagina que esta situacin instancia de un objeto que no responde a minutos, acabamos escribiendo el
ocurre en un programa que has creado. la suma con un nmero. cdigo del Listado 1. Pero nos interrum-
Mientras tecleas piensas, mivariable Solucionar un BUG semntico puede pen antes de probar la funcin, y
contiene una distancia y operas con ella, llevar desde segundos a aos. De hecho, cuando volvemos al ordenador y ejecu-
slo que, sin que te des cuenta, en reali- existe mucho software, tanto comercial tamos un programa de prueba se queda
dad mivariable contiene un objeto de la como libre, con BUGS que no han sido bloqueado! No puede ser!, en tan pocas
clase Persona qu pasara si intentas resueltos en aos. Ahora que el pro- lneas de cdigo no puede haber un
sumarle 18? blema ha sido planteado con ms deteni- error tan grave. Despus de unos minu-
miento, conviene ver con qu arsenal tos de frustracin, abandonamos la ins-
>>> a + 18 contamos en nuestra batalla contra los peccin visual y pasamos a trabajar
Traceback (most recent call last): BUGS. PDB.
File <stdin>, line 1, in ? PDB es el Python DeBugger y viene de
TypeError: unsupported operand Depurando Cdigo serie con Python. Eso est muy bien,
type(s) for +: instance and int Digamos que estamos haciendo un script porque en caso de necesitarlo siempre lo
>>> para mostrar, usando como caracteres las tendremos a mano. A diferencia de otros
debuggers, PDB se puede usar como si
Listado 1: El Cdigo Nefasto fuese una librera. Podemos integrarla en
nuestros programas y eliminarla cuando
01 #!/usr/local/bin/python 14 if ((i >= inf) and (i <= sup)):
ya los hayamos arreglado. Como es posi-
02 15 encontrado = True
ble observar en el Listado 1, hemos
03 import pdb 16 else:
importado PDB y hemos pasado como
04 17 k+=1
parmetro a pdb.run() una cadena en la
05 def vacaciones (l): 18
06 cadena = 19 if (encontrado):
que invocamos la funcin vacaciones()
07 for i in range(1,31): 20 cadena += # con un argumento acorde. Si ejecutamos
08 encontrado = False 21 else: el programa, veremos lo siguiente en
09 max = len(l) 22 cadena += nuestro terminal:
10 k=0 23
11 while(not(encontrado) or 24 return cadena josemaria@linuxmagazine$U
k<max): 25 ./p.py
12 rango = l[k] 26 pdb.run(vaca- > <string>(1)?()
13 inf,sup=rango ciones([(1,3),(6,10)]))
(Pdb)

16 PYTHON 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:

(Pdb) h l
l(ist) [first [,last]]
List source code for the current
file.
Without arguments, list 11 lines
around the current line Figura 3: El depurador de IDLE.
Figura 2: IDLE no es bello, pero es prctico. or continue the previous listing.
With one argument, list 11 lines (Pdb) s
Muy bien, el PDB comienza a hacer su starting at that line. > /home/josemaria/p.py(7)
trabajo. En lugar de no hacer nada, como With two arguments, list the given vacaciones()
hara la funcin vacaciones() si el pro- range; -> for i in range(1,31):
grama se limitase a ejecutarla, entramos if the second argument is less than (Pdb) s
en lo que parece el prompt del shell de the first, it is a count. > /home/josemaria/p.py(8)
PDB. Esta shell tiene sus propios coman- (Pdb) vacaciones()
dos, no tiene nada que ver con Python. -> encontrado = False
Para ver los comandos disponibles pode- El comando l sirve para mostrar visual- (Pdb) s
mos emplear el comando h: mente en qu parte del cdigo estamos > /home/josemaria/p.py(9)
en un momento dado; como nos pica la vacaciones()
(Pdb) h curiosidad ejecutamos l: -> max = len(l)
(Pdb)
Documented commands (type help (Pdb) l
<topic>): [EOF] Hemos avanzado 4 pasos y puede que
================================ nos hallamos perdido. Dnde estamos?
EOF break condition disable help Vaya, resulta que no hemos comenzado Qu estamos haciendo? Hacia dnde
list q step w an, por lo que no estamos en ninguna vamos? El comando l resuelve todas
a bt cont down ignore n quit parte. El comando que probablemente nuestras dudas:
tbreak whatis usemos ms a menudo es s Por qu?
alias c continue enable j pues porque es el comando que hace que (Pdb) l
next r u where PDB avance una lnea y la ejecute: 4
args cl d exit jump p return 5 def vacaciones (l):
unalias (Pdb) s 6 cadena =
09 b clear debug h l pp s up --Call-- 7 for i in range(1,31):
> /home/josemaria/p.py(5)U 8 encontrado = False
Miscellaneous help topics: vacaciones() 9 -> max = len(l)
========================== -> def vacaciones (l): 10 k=0
exec pdb 11 while(not(encontrado)
Muy bien, comenzamos a ejecutar el or k<max):
Undocumented commands: trozo de cdigo Python que pasamos a 12 rango = l[k]
====================== pdb.run(). Por el momento no pasa nada 13 inf,sup=rango
retval rv interesante, aunque estara bien ver qu 14 if ((i >= inf) and
contiene la variable l, para ello podemos (i <= sup)):
(Pdb) usar el comando p que hace las funciones (Pdb)
de print:
Buff! estos son demasiados comandos. Ya me sito, acabamos de entrar en el
Como suele ocurrir la primera vez que (Pdb) p l bucle for, avancemos un poco ms:
un principiante en Linux pulsa dos [(1, 3), (6, 10)]
veces tabulador en BASH, lo que vemos (Pdb) s
nos asusta. En este caso no son tantos Efectivamente, l contiene el parmetro > /home/josemaria/p.py(10)U
comandos (en un Linux estndar hay que hemos pasado a la funcin. Ya esta- vacaciones()
miles de ejecutables), pero s ms extra- mos en ruta, as que avancemos unos -> k=0
os. Debuggear es algo que SIEMPRE se cuantos pasos ms: (Pdb) s
hace bajo presin, por lo que todo el > /home/josemaria/p.py(11)U
tiempo que ahorremos es oro. As que (Pdb) s vacaciones()
los creadores de PDB nos ahorran > /home/josemaria/p.py(6) -> while(not(encontrado) orU
segundos reduciendo los comandos a vacaciones() k<max):
letras. -> cadena = (Pdb)

W W W. L I N U X - M A G A Z I N E . E S PYTHON 17
INTRODUCCIN Debugging

Bueno, llegamos a una encrucijada. variable encontrado sea True. Si todo va


Cuando ejecutamos la funcin sin PDB, bien, el siguiente paso despus de eva-
parece como si el programa nunca aca- luar las condiciones del while ser salir
base. Esto implica que hay algo que se del mismo y pasar a las siguiente instruc-
repite eternamente. En este programa hay cin fuera del while:
dos bucles, que son los nicos elementos
que pueden repetirse eternamente. El pri- (Pdb) s
mero es un bucle for con un principio, 1, > /home/josemaria/p.py(12)U
y un fin, 31, por lo que podemos descar- vacaciones()
tarlo como culpable. El segundo sospe- -> rango = l[k]
choso es ese bucle while, que en un pri- (Pdb)
mer momento no tiene porqu acabar,
puede que jams pare. Si un bucle while Pero qu ocurre aqu? Esto no debera
no para es porque las condiciones que lo pasar. La nica explicacin posible es Figura 4: El seudo-editor de IDLE.
controlan siempre se dan. En este cdigo que la condicin del while est es eso
se supone que el if dentro del bucle while un or? Debera ser un and! Deberamos mos seleccionar en el men Run la
hace que ste pare alguna vez, as que salir si hemos encontrado que el da per- opcin Run Module. Con este paso carga-
algo debe fallar ah dentro. Comencemos tenece a un rango, o si no quedan rangos remos el fichero y comenzaremos a eje-
comprobando los valores de las variables que comprobar. Ah estaba nuestro BUG, cutarlo. Como antes seleccionamos la
que controlan el bucle: tres simples letras, se cambian y pro- opcin Debugger, IDLE se cargar en la
blema solucionado. ventana de debugging y podremos
(Pdb) p encontrado Ahora que ya sabemos lo que pasa, comenzar a visionar la evolucin del
False slo queda salir del debugger usando el programa conforme pulsemos sobre el
(Pdb) p k comando q. Nuestro nuevo cdigo ya botn Step. No es que IDLE sea un gran
0 est listo para ser usado (ver Figura 1). avance respecto al uso de PDB, pero
(Pdb) p max desde luego simplifica el debugging.
2 Y Ahora de Forma Fcil
(Pdb) No hay una manera ms moderna de Conclusin
conseguir esto mismo? Pues s, gracias a Los debuggers no son una excusa para
De acuerdo, todo parece en su sitio. Si IDLE (ver Recurso [4]). crear programas sin fijarnos demasiado
avanzamos un poco: IDLE es el entorno de desarrollo que en los problemas. Pero si no tenemos
viene, tambin, junto a Python. En la claro qu est ocurriendo o si ya no
> /home/josemaria/p.py(12) Figura 2 se puede observar el aspecto sabemos qu hacer, entonces un debug-
vacaciones() que tiene IDLE una vez que se ejecuta. ger como PDB puede ayudarnos a tener
-> rango = l[k] No es ninguna belleza, pero es prctico, una imagen ms clara de lo que pasa en
(Pdb) s reemplaza al intrprete de comandos de nuestro programa. Existen bugs que no
> /home/josemaria/p.py(13) Python y simplifica algunas tareas. pueden cazarse con PDB, pero son tan
vacaciones() En particular, estamos interesados en extraordinariamente raros, que es posi-
-> inf,sup=rango cmo puede simplificar el debugging. ble que jams nos encontremos con
(Pdb) s Para ello slo tenemos que ir al men uno.
> /home/josemaria/p.py(14) Debug que aparece en la barra de men Los debuggers nos permiten progra-
vacaciones() de IDLE y activar Debugger. Aparecer mar sin miedo a no entender lo que esta-
-> if ((i >= inf) and una ventana como la que puede obser- mos haciendo, y es precisamente eso lo
(i <= sup)): varse en la Figura 3. El lector no debe que nos permitir avanzar y aprender
(Pdb) s extraarse demasiado con esta nueva ms rpidamente. Perdmosle el miedo
> /home/josemaria/p.py(15) ventana, viene a condensar en un solo a los BUGS!
vacaciones() lugar todo lo que hemos visto sobre
-> encontrado = True PDB: hay un botn llamado STEP, que Recursos
(Pdb) s nos permitir avanzar en el cdigo paso
[1] Documentacin del depurador PDB:
> /home/josemaria/p.py(11) a paso, y tambin hay un rea llamada
http://docs.python.org/lib/module-
vacaciones() Locals, donde iremos viendo el valor de
pdb.html
-> while(not(encontrado) or las variables que se vayan declarando en
[2] Valgrind captura errores que compro-
k<max): el cdigo, de forma que podremos ir con-
meten la memoria en:
(Pdb) trolando la evolucin del programa de un
http://valgrind.org/
solo vistazo.
[3] Data Display Debugger:
Resulta que hemos entrado en una tupla Para ello slo tenemos que cargar el
http://www.gnu.org/software/ddd/
que representa unas vacaciones, el da programa en IDLE y veremos cmo se
representado por i pertenece a las vaca- abre una especie de editor de textos [4] El entorno de desarrollo IDLE:
http://www.python.org/idle/
ciones. Por tanto, hemos hecho que la como el de la Figura 4, en el que debere-

18 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Funciones Lambda AVANZADO

Funciones lambda en Python

s
tyl
ep
Sin

ho
tog
rap
hs
12-
3R
F.c
om
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 cin especial, para la que us la letra >>> a = lambda x : x + 1
los conceptos de funcin lambda y al lambda. Las funciones lambda que vere- >>> a(2)
concepto de cierre. Siempre que aparece mos comparten slo algunas de las 3
un nuevo lenguaje de programacin caractersticas que Alonzo defini para >>>
suele venir acompaado de una discu- las suyas, y podemos decir que lo que las
sin que demuestra que el nuevo len- define es que son annimas: las funcio- Recordemos que en Python las variables
guaje es mejor porque incorpora alguno nes lambda no tienen nombre. no son ms que nombres que asignamos
de estos dos conceptos. Quien est un Cmo es esto posible? Para qu que- a cosas y no contenedores de esas cosas.
poco al tanto de los ltimos avances rramos algo as? Son slo dos preguntas Esta diferencia es vital para comprender
habr escuchado decir que C# incorpora que trataremos de resolver en este art- por qu podemos asignar una funcin a
las funciones lambda y que la prxima culo mientras desmitificamos un con- una variable y posteriormente asignar un
versin java al fin las tendr entre su cepto tan abstracto a primera vista. valor a la misma (ver Figura 1).
arsenal. En este sencillo ejemplo hemos creado
Las funciones lambda reciben su nom- Las Funciones Lambda una funcin que suma el nmero 1 al
bre de la teora del clculo lambda de Las funciones lambda son el concepto nmero que pasemos como argumento.
Alonzo Church [1], que junto a Alan ms sencillo de los que vamos a ver en La definicin de una funcin lambda
Turing, sentaron las bases de la teora de este artculo. Python las soporta desde siempre comienza con la palabra lambda
computacin. Mientras Turing utiliz en hace un buen tiempo, y se encuentran seguida de los argumentos que vamos a
su teora una mquina abstracta e imagi- integradas en la librera base de Python. aceptar. Detrs de los argumentos usa-
naria a la que se llam Mquina de Tambin conocidas como funciones an- mos el smbolo : para separar la defini-
Turing, Alonzo utiliz un enfoque ms nimas, no son ms que funciones sin cin del cuerpo de la funcin. Las fun-
tradicional, creando una serie de reglas nombre que podemos crear en cualquier ciones lambdas, al ser annimas, deben
que permitan realizar computaciones. momento y pasar como argumento a almacenarse en una variable si quere-
Su sistema requera de un tipo de fun- otras funciones: mos reutilizarlas, y se comportarn

W W W. L I N U X - M A G A Z I N E . E S PYTHON 19
AVANZADO Funciones Lambda

como una funcin tradicional a la que >>> d = lambda x,y: x*y 06 self.nombre = nombre
podremos llamar pasndole parmetros >>> d(2,3) 07 self.edad = edad
entre dos parntesis (). 6 08
Existen varias restricciones en el uso >>> 09 def __eq__(self, otro):
de las funciones lambda. La primera es 10 return cmp(self.edad,
que siempre deben devolver un valor. Podemos, de forma limitada, emplear otro.edad)
Son funciones en el sentido estricto de sentencias condicionales, puesto que 11
las matemticas, aceptan valores, los Python nos permite usar la frmula 12 def __lt__(self, otro):
transforman y devuelven algn valor. En if...else como si fuese una expresin: 13 return self.edad <
Python podemos devolver varios valores otro.edad
si lo deseamos: >>> d = lambda x: Yuju U 14
if x > 2 else ooooh 15 def __str__(self):
>>> b = lambda x: (x,x+1) >>> d(2) 16 return self.__repr__
>>> b(2) ooooh 17
(2,3) >>> d(3) 18 def __repr__(self):
>>> Yuju 19 return u({0} tiene {1})
.format(self.nombre,
La segunda restriccin es que slo pue- Pero Para qu Sirven? self.edad)
den contener una expresin. Esta restric- Las limitaciones a las funciones lambda 20
cin limita bastante el poder de las fun- en Python tienen un objetivo bien defi- 21 l = [Edad(Luis, 65),
ciones lambda en Python. En Ruby, por nido: evitar el mal uso que se puede 22 Edad(Juan,28),
ejemplo, las funciones lambda (tambin hacer de ellas. Se restringe su uso a 23 Edad(Montse, 33),]
llamadas bloques) pueden contener tan- aquellas funciones donde es necesario 24
tas expresiones como deseemos. En pasar operaciones sencillas que el dise- 25 print sorted(l)
Python se decidi aadir esta restriccin ador original de una funcin no puede
para que los desarrolladores terminaran predecir de antemano. Por ejemplo, si O bien definimos una funcin que nos
empleando las funciones lambda all queremos ordenar una lista, la funcin permita extraer el valor a comparar de
donde una funcin tradicional podra de ordenacin sorted() intentar compa- los objetos:
valerles (en Javascript es algo que se rar los elementos de la misma usando los
hace habitualmente). Cuando violemos mtodos que existen por defecto. Pero >>> def mi_ordenacion (x): U
una de estas restricciones, Python gene- qu ocurre si los datos a ordenar son return x[1]
rar una excepcin: algo especiales? Imaginemos que tene- >>> sorted(l, key = U
mos una lista de datos donde cada ele- mi_ordenacion)
>>> c = lambda x: y = x+1 mento es un tupla con el nombre y la [(Juan,28),(Montse, 33), U
File <stdin>, line 1 edad de una serie de personas: (Luis, 65)]
c = lambda x: y = x+1 >>>
^ >>> l = [(Luis, 65), U
IndentationError: unexpected U (Juan,28),U O bien podemos emplear una funcin
indent (Montse, 33)] lambda para generar los valores a com-
parar:
En este caso hemos tratado de realizar Cmo podemos indicar a sorted que
una asignacin dentro de una funcin queremos ordenar la lista por edades? El >>> sorted(l, key = U
lambda. Lo que s podemos hacer es diseador de sorted no puede predecir lambda x: x[1])
pasar ms de un parmetro a la fun- todas las posibles estructuras de datos [(Juan,28),(Montse, 33), U
cin: que se pasarn a la funcin. Existen tres (Luis, 65)]
opciones, u obligamos a la >>>
persona que pasa los datos
a encapsularlos en objetos La primera opcin, ms clsica de len-
con un mtodo que nos guajes como Java o C#, y (supuesta-
permita comparar dos mente) ms limpia, tiene un gran pro-
objetos: blema. Qu ocurre si queremos ordenar
los datos de varias maneras diferentes?
01 from functools import El diseador de sorted slo emplear una
total_ordering de ellas. Es un enfoque bastante inflexi-
02 ble, es preferible que la funcin que
03 @total_ordering selecciona el criterio de ordenacin sea
04 class Edad(object): externa al objeto a ordenar. Adems,
05 def __init__(self, como se puede observar, esta opcin es
Figura 1: Las variables en Python son nombres. nombre,edad): bastante ms compleja.

20 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Funciones Lambda AVANZADO

La segunda opcin implica el uso de el valor devuelto puede


una funcin externa que nos devuelve el almacenarse en una varia-
valor a comparar para cada objeto. Indi- ble y se comportar como
camos a sorted qu funcin usar para una funcin. El secreto
seleccionar los valores a comparar est en pasar a la funcin
mediante el parmetro key. Por ltimo lambda el valor num que
vemos cmo se hara lo mismo con una queda definido como valor
funcin lambda. Salta a la vista que la por defecto para x. Lo
tercera opcin es la corta, fcil de leer y curioso es que en este caso
elegante (o lo que es lo mismo, la ms Python no nos permite
pythonic). pasar ningn valor a la
El lenguaje de programacin Common funcin lambda una vez
Lisp se enfrent a este mismo problema definida. Si tratamos de
cuando se dise su sistema de objetos, pasar un valor:
y la solucin fue la misma: sacar fuera
del objeto y de la funcin de ordenacin >>> cierre(10)
el cdigo que genere los datos a compa- 4
rar. Python emple la misma tcnica, por >>>
lo que mucha gente ve semejanzas entre
ambos lenguajes de programacin. Nos sigue devolviendo 4! Figura 2: Cmo funciona un cierre en torno a una funcin.
No importa qu valor pase-
Cierres mos a la funcin, el contenido de x ha 07 >>> saludo = Saluda(Hola)
Otro de los conceptos que puede provo- quedado completamente cerrado y blo- 08 >>> saludo(mundo)
car ms de un dolor de cabeza es el de queado. Por as decirlo, su valor se ha 09 Hola mundo
cierre. Lenguajes como Javascript giran vuelto inaccesible. Si quisiramos poder 10 >>> saludo = Saluda(Hello)
en torno a este concepto, ya que les per- cambiar el valor de una variable cerrada, 11 >>> saludo(world)
mite crear algo parecido a objetos, ver deberamos usar una funcin normal en 12 Hello world
Figura 2. En Python, sin embargo, los lugar de una funcin lambda, porque 13 >>>
cierres son la base de una de las caracte- como ya vimos, las funciones lambda no
rsticas ms usadas del lenguaje ltima- aceptan asignacin de variables. Creamos un objeto tradicional con la
mente: los decoradores. Tiene sentido usar cierres en Python? nica diferencia de poseer un mtodo
Un cierre es un trozo de cdigo fuente El sistema de objetos de Python es muy llamado __call__, que ser el que se eje-
que depende de variables, algunas de las sencillo y nada engorroso, por lo que es cute cuando invoquemos la instancia de
cuales han sido capturadas junto al muy extrao ver el uso de cierres en la clase como si fuese una funcin. Pri-
trozo de cdigo y quedan aisladas de Python, salvo por una excepcin: los mero debemos generar una instancia a la
cualquier interferencia externa. En el decoradores. Un decorador es una fun- que pasamos la variable de se cerrar y
caso de Javascript, que no posee objetos cin que intercepta los parmetros de posteriormente podemos invocar la ins-
propiamente dichos, se usan cierres para otra funcin, hace algo y devuelve la tancia como si fuese una funcin que
que una serie de funciones compartan funcin original. Normalmente no se cie- acepta parmetros como cualquier otra
unas variables que no pueden ser accedi- rran variables en un decorador, pero es funcin.
das desde fuera y que por tanto estn posible hacerlo.
protegidas. La respuesta de Python a los cierres de Funciones de Primer Orden
En Python los cierres no funcionan funciones son los objetos callable. En Como ya hemos dicho, las funciones
exactamente como lo hacen en otros len- Python es posible crear un objeto que se lambda no son especialmente potentes
guajes. Siguiendo con las funciones comporte como una funcin. La funcin en Python en Ruby lo son ms pero
lambda, vamos a crear un cierre con una tendr acceso a una serie de variables de nos permiten pasar comportamientos a
de ellas: instancia que se pasan en el constructor otras funciones. En teora de lenguajes
de la misma: de programacin, se llama funcin de
>>> def crea_cierre(num): primer orden a aquella funcin que
... return lambda x=num: x+1 01 >>> class Saluda(object): acepta otra funcin como parmetro,
... 02 ... def __init__(self, acepta comportamientos adems de
>>> cierre = crea_cierre(3) saludo): datos, lo que la hace especialmente
>>> cierre() 03 ... self.saludo = potente y flexible.
4 saludo En Python es absolutamente normal
>>> 4 04 ... def __call__(self, usar funciones como parmetros:
nombre):
Analicemos este cdigo. La funcin 05 ... print {0} {1} 01 def saluda(x,
crea_cierre() acepta un parmetro num y .format(self.saludo, nombre) f=None):
devuelve una funcin lambda, por lo que 06 ... 02 if f:

W W W. L I N U X - M A G A Z I N E . E S PYTHON 21
AVANZADO Funciones Lambda

03 print f(x) metros. Primero aplicar la


04 else: funcin a Luis y 65,
05 print x despus a Juan y 28 y
06 >>> as indefinidamente. map
07 >>> saluda(hola) no est limitada a dos listas,
08 hola podemos emplear tantas lis-
09 >>> saluda(hola, tas como queramos:
f=lambda x: x.upper())
10 HOLA >>> map(lambda x:
11 >>> x.upper(),U
[Luis, JuanU
Al fin y al cabo, las variables en Python , Montse])
no son ms que nombres que apuntan a [LUIS, JUAN, U
cosas, sin importar demasiado qu MONTSE]
son esas cosas. No hay gran misterio en >>> map(lambda x,y,z:U
las funciones de primer orden vistas as. (x, y*z),U
Es lo que haces con ellas lo que las [Luis, U
vuelve interesantes. Python nos provee Juan,Montse],U
de un conjunto de funciones de primer [65,28,33], [1,2,3])
orden, heredadas de otros lenguajes de [(Luis, 65),U
programacin funcionales, que nos per- (Juan, 56),U
miten emplear funciones en sus opera- (Montse, 99)]
ciones. >>>

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


Cuando las funciones lambda realmente cualquier funcin previa-
brillan es cuando se usan en conjuncin mente definida, estamos pasando funcio- mayor de ellos. Por lo tanto, map aplica
con las funciones de filtrado y mapeo. nes lambda. Es la manera ms sencilla y una funcin a una gran cantidad de
Con todo el revuelo generado por Goo- rpida de aplicar map, aunque como ya datos y reduce realiza alguna operacin
gle y otras empresas en torno a las tcni- hemos dicho antes, si necesitamos ms sobre los datos que los convierte en un
cas Map/Reduce, es interesante recordar de una operacin o el cdigo es complejo solo valor.
los humildes comienzos de estas tcni- es mejor definir la funcin. A pesar de toda la fanfarria existente
cas. Este map es el mismo que el del alrededor de Map/Reduce, lo cierto es
El filtrado y mapeo son tcnicas here- famoso Map/Reduce de Google, con la que en Python se usan poco. De hecho,
dadas de la programacin funcional, ver diferencia de que el de Google se aplica a reduce dejar de ser una funcin primi-
Figura 3. En este estilo de programacin cientos o miles de mquinas, que aplican tiva del sistema en Python 3 y pasar a
se evita modificar los datos originales, y funciones map muy complejas a gran ser una funcin de la librera functools,
en lugar de ello se realizan una serie de cantidad de datos. Pero el concepto es el por lo que ha sido degradada a ciuda-
transformaciones sobre los datos para ir mismo. dano de segunda fila.
reducindolos y operando sobre los Si este es el map dnde est
resultados hasta conseguir el resultado reduce? Conclusin
deseado. El cdigo resultante suele ser Estamos tan acostumbrados ya a la pro-
conciso, aunque no siempre fcil de leer >>> reduce(U gramacin orientada a objetos, que se
y entender. La programacin funcional lambda x,y: x if x[1] > y[1] U nos olvida que Python en sus inicios
tambin hace uso de las funciones de else y,U tom prestados gran cantidad de concep-
primer orden que hemos visto antes. [(Luis, 65), (Juan, 56),U tos y tcnicas de otros modelos de pro-
Comencemos por echar un vistazo a la (Montse, 19)]) gramacin. Las funciones lambda no
funcin map: (Luis, 65) estn muy vistas como tcnica de pro-
>>> gramacin, pero las restricciones a las
>>> map(lambda x,y: (x,y),U que las somete Python las ha domesti-
[Luis, Juan,Montse], U reduce va a aplicar una funcin de 2 cado lo suficiente como para que en
[65,28,33]) variables a los elementos de una lista lugar de entorpecer nuestro cdigo lo
[(Luis, 65), (Juan, 28), U con el objetivo de acabar devolviendo un hagan ms ligero y sencillo de compren-
(Montse, 33)] solo elemento. Como su propio nombre der.
>>> indica, reduce una lista a un elemento.
Aqu estamos buscando el valor ms Recursos
map aplica una funcin a dos listas de grande de los presentes en la lista, por lo
valores, recorriendo ambas listas de que la funcin reductora compara los [1] Alonzo Church: http://es.wikipedia.
org/wiki/Alonzo_Church
valores y pasando los valores como par- dos parmetros y siempre devuelve el

22 PYTHON 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? Nos-

otros haremos lo mismo con 70 lineas de cdigo Python, OpenOffice y la tecnologa

UNO de OpenOffice. Por Jos Mara Ruz

sonas supone ya una locura slo por el Pero los sistemas de componentes
hecho de asegurarse que todos cobren. tambin se emplean en software libre y
Pero vayamos a nuestro mundillo han dado buenos resultados. Quizs el
cmo podemos organizarlos para que ms desconocido es UNO, de Universal
el desarrollo no acabe en un fiasco? Network Objects, el sistema que emplea
Esta es la gran cuestin no resuelta OpenOffice, ver Listado [1], y que SUN
de la informtica, pero, aunque no desarroll para su precursor: StarOffice.
hayamos encontrado una solucin
fiable, s se disponen de tcnicas que PyUNO
aumentan la probabilidad de que, al Un sistema de componentes con el que
menos, se cree algn software til. slo se pueda programar en un
No es ni ser la ltima vez que desde Una de estas tcnicas consiste en lenguaje no tiene mucha utilidad. Por
esta seccin recordemos que la idea ori- emplear un sistema de componentes eso en OpenOffice se han asegurado de
ginal de Stallman era la de que cada como base para el desarrollo. Un com- fomentar la creacin de interfaces a
programa libre estuviese construido ponente es una cantidad de software distintos lenguajes de programacin.
sobre libreras de funciones, de manera que ofrece un servicio bien definido y Podemos acceder a UNO usando
que su cdigo fuese reutilizable por que es reutilizable. Adems debe ser Javascript, Java, Ruby, Perl o Python
cualquier otro programa. posible reutilizarlo de verdad: desde (ver Recurso [2]).
Quizs en un programa pequeo no cualquier lenguaje y cualquier sitio. PyUNO es el nombre de la interfaz y
sea muy til este tipo de diseo, pero Cualquiera que tenga conocimiento podremos encontrarlo sin problemas en
qu pasa con esos monstruos consum- sobre cmo funcionan los lenguajes de nuestra distribucin de Linux. Eviden-
idores de memoria que rondan por programacin a bajo nivel sabr que temente, necesitamos tambin tener
nuestros discos duros? Nos referimos a esto es muy muy complicado. Por ello instalado OpenOffice. En este artculo
programas o entornos del calibre de se han desarrollado infraestructuras hemos realizado los programas usando
Gnome, KDE, Mozilla u OpenOffice. que nos permiten interactuar con los OpenOffice 2.0, que cambi la interfaz
Todo el mundo se queja de su tamao componentes de manera indirecta. A respecto a la versin 1.0, y la versin
excesivo, su alto consumo de recursos este software se le suele llamar mid- de PyUNO 0.7.0.
y su inexplicable complejidad. dleware (algo as como software de
Quizs con este artculo desminta- en medio). Un Ejemplo Rpido
mos este mito y hagamos que el lector Ejemplos famosos de Middleware son Vamos a crear el famoso Hola mundo
mire con nuevos ojos a estos maravi- J2EE, que muchos conocern, y con PyUNO. Para ello primero debemos
llosos programas. CORBA, que a muchos les gustara no arrancar OpenOffice con el siguiente
conocer. Ambos son sistemas enormes comando desde el directorio donde est
Grandes Sistemas de Componentes y costosos que relegan al programador instalado:
El diseo de un gran programa puede a mera herramienta en manos de inge-
llevar aos y cientos o miles de progra- nieros, denominados arquitectos, que $> ./sofficeU
madores. Organizar tal cantidad de per- conocen su compleja infraestructura. "-accept=socket,U

W W W. L I N U X - M A G A Z I N E . E S PYTHON 23
INTEGRACIN OpenOffice

El proceso es el siguiente:
Listado 1: Programa Hola Mundo Obtenemos un contexto local (un
01 import uno sitio donde guardar los datos de la
02 conexin)
03 localContext = uno.getComponentContext()
Arrancamos el componente UnoUrl-
04 resolver = localContext.ServiceManager.createInstanceWithContext
(com.sun.star.bridge.UnoUrlResolver, localContext ) Resolver que nos sirve para acceder
05 ctx = resolver.resolve( uno:socket,host=localhost,port=2002;urp; a otro OpenOffice en otro equipo
StarOffice.ComponentContext ) (en nuestro caso accederemos a
06 desktop = ctx.ServiceManager.createInstanceWithContext( nuestro propio equipo)
com.sun.star.frame.Desktop,ctx)
07 doc = Emplearemos el objeto resolver para
desktop.loadComponentFromURL(private:factory/swriter,_blank,0,()) acceder al OpenOffice remoto
08 cursor = doc.Text.createTextCursor() Arrancamos un Desktop (escrito-
09 doc.Text.insertString( cursor, Hola Mundo, 0 ) rio) de OpenOffice (esto es, una
10 ctx.ServiceManager
instancia de OpenOffice vaca)
Arrancamos un SWriter (es decir, el
host=localhost,U OpenOffice no abre el socket, de ma- procesador de textos) en el escrito-
port=2002;urp;" nera que no podrn controlar nuestro rio
OpenOffice sin nuestro consentimiento. Obtenemos un cursor, con el que
Al arrancar OpenOffice se arranca su OpenOffice incorpora de serie varios podremos posicionarnos dentro del
sistema de componentes. Podemos pen- intrpretes de lenguajes, entre ellos texto
sar en este proceso como en el uno de Python que ya viene preconfig- e insertamos texto en el cursor
arranque de un servidor, slo cuando urado para poder hacer uso de la libr- El resultado, no muy espectacular,
est funcionando podrn los clientes era UNO. Est junto al resto de eje- podemos verlo en la Figura [1]. Ya te-
trabajar con l. cutables de OpenOffice, as que lo eje- nemos nuestro hola mundo insertado
Las opciones que pasamos son para cutaremos desde all. El programa que en SWriter.
que se cree un socket y se escuche en usaremos se encuentra en el Listado Demasiado cdigo? Piensa por un
localhost en el puerto 2002. Por defecto [2]. momento lo que estamos haciendo.

Listado 2: OfficeBroker
01 import uno setString(entrada)
02 import random 27 else:
03 import time 28 self.s1.getCellByPosition(i,fila).setValue
04 import httplib (float(entrada))
05 import csv 29
06 30 i = i + 1
07 class Calc: 31
08 def __init__(self): 32 def getSimbolo(simbolo):
09 self.conecta() 33 c = httplib.HTTPConnection(finance.yahoo.com)
10 34 c.request(GET,
/d/quotes.csv?s=+simbolo+&f=sl1d1t1c1ohgv&
11 def conecta (self): e=.csv)
12 self.local = uno.getComponentContext() 35 r = c.getresponse()
13 self.resolver = self.local.ServiceManager. 36 cad = r.read()
createInstanceWithContext
(com.sun.star.bridge.UnoUrlResolver, self.local) 37 reader = csv.reader([cad])
14 self.context = 38 resultado = []
self.resolver.resolve(uno:socket,host= 39 for row in reader:
localhost,port=2002; 40 resultado = row
urp;StarOffice.ComponentContext)
41 return resultado
15 self.desktop = self.context.ServiceManager.
createInstanceWithContext 42
(com.sun.star.frame.Desktop, self.context) 43 if __name__ == __main__:
16 #self.doc = self.desktop.getCurrentComponent() 44
17 self.doc = self.desktop.loadComponentFromURL 45 simbolos = [GOOG,MSFT,RHAT]
(private:factory/scalc,_blank,0,()) 46
18 self.hojas = self.doc.getSheets() 47 c = Calc()
19 self.s1 = self.hojas.getByIndex(0) 48
20 49 while(1):
21 def actualiza(self, cotizacion, fila): 50 i = 0;
22 51 for s in simbolos:
23 i = 0 52 c.actualiza(getSimbolo(s),i)
24 for entrada in cotizacion: 53 i = i + 1
25 if (i == 0) or (i == 2) or (i ==3): 54
26 self.s1.getCellByPosition(i,fila). 55 time.sleep(10)

24 PYTHON W W W. L I N U X - M A G A Z I N E . E S
OpenOffice INTEGRACIN

Figura 1: Un documento de Write de OpenOffice con el ineludible Figura 2: El programa examina los valores de la bolsa NASDAQ de
Hello World generado a partir de PyUNO. Yahoo a intervalos regulares y los inserta en una hoja de clculo.

Hemos levantado dos componentes y lar ese servicio a algn componente Ahora ya tenemos los datos, tenemos
hecho acceso remoto a otro OpenOf- grfico, como por ejemplo un botn o que mandarlos a SCalc. Hemos creado
fice. Este segundo OpenOffice puede men. un objeto llamado Calc para gestionar
estar en una mquina al otro lado del Comenzaremos por realizar un pro- el acceso. Hemos metido en el mtodo
mundo. Es algo bastante impresionan- grama que funcionar de manera constructor (__init__) el cdigo que
te, pero por el momento poco til. externa a OpenOffice, y despus creare- conecta con OpenOffice, puesto que
Veamos un poco ms sobre UNO antes mos un componente con l y lo inte- slo lo haremos una vez. Una hoja de
de realizar un programa ms til. graremos en OpenOffice. clculo posee varias hojas, as que
tendremos que solicitar una usando el
Arquitectura de UNO Nuestro Programa de Stocks mtodo getSheets(), que nos devuelve
OpenOffice est implementado en Comencemos con la parte til, ver Lis- una lista con las distintas hojas. Dentro
C++. UNO se usa internamente para tado [2]. Vamos a crear un sistema que de esta lista usaremos getByIndex()
realizar cualquier cosa. Bsicamente, nos permita controlar las acciones de para seleccionar la primera hoja, que
OpenOffice no es ms que una gran una serie de empresas que estn en es la que se ve cuando arrancamos
cantidad de componentes que interac- bolsa dentro de un ndice tecnolgico, SCalc.
tan entre s. Todo dentro de OpenOf- el Nasdaq (para algo estamos en una El mtodo actualiza() admite una
fice es un componente, as que revista de informtica), usando la hoja lista con los datos de cotizacin y
podemos acceder a cualquier parte de de clculo SCalc y un programa Python. nmero que representa la fila donde
la aplicacin, incluso reconstruir Nuestro programa acceder usando aparecer en SCalc. Una hoja de clculo
OpenOffice en Python! Internet a un sitio web donde podr se compone de celdas, y stas tienen un
Los sistemas de componentes usan recoger los datos en CSV (Valores Sepa- tipo. La funcin getCellByPosition() nos
un registro de componentes al que se le rados por Comas), los procesar y los permite acceder a una celda pasndole
puede pedir que arranque compo- introducir en SCalc. la columna y la fila (al revs de lo nor-
nentes. El registro localiza el compo- Comenzaremos por crear una fun- mal, as que cuidado).
nente en disco y lo carga en memoria, cin que recoja el fichero CSV y lo pro- Una vez localizada la celda, tenemos
de manera que puede ser usado. Las cese. Lo que hacemos es conectarnos varias funciones para poder asignar un
llamadas a las funciones no se realizan con la pgina web finance.yahoo.com. valor:
directamente, sino que se suele Yahoo tiene un sitio web bastante setString(): para poner una cadena
emplear algn sistema no dependiente avanzado sobre cotizaciones de bolsa, setValue(): para poner un nmero
de lenguaje o plataforma, como puede y uno de sus servicios nos permite setFormula(): para poner una fr-
ser XML o un formato ASCII. recoger los datos de cotizacin de una mula
El registro tambin debe ser capaz de empresa en tiempo real en formato El dato cotizacin es la lista de
gestionar los recursos que consume el CSV. Sin embargo, Yahoo no nos per- parmetros de cotizacin, pero vienen
componente, descargndolo de memo- mitir acceder a los datos demasiado a dados como cadenas de caracteres. Las
ria cuando ya no sea necesario. menudo, ya que dispone de un servi- posiciones 0, 2 y 3 son realmente cade-
Los componentes pueden ser progra- cio de pago para ello, as que puede nas, pero el resto son nmeros. Por eso
mados en cualquier lenguaje con el que cortarnos el grifo en cualquier tenemos que convertir ciertos valores
se tenga interfaz. Un componente es un momento si hacemos demasiadas con- al tipo float() mediante la funcin
conjunto de ficheros que proporcionan sultas por minuto. Por eso recogere- float().
un servicio. Se acompaan de un mos los datos cada 10 segundos. La El resultado se puede ver en la Figura
fichero XML que describe su funcional- funcin getSimbolo() se encargar de [2]. Observamos cmo se abre una ven-
idad. Lo mejor es que podemos vincu- ello. tana de SCalc y se rellena con los val-

W W W. L I N U X - M A G A Z I N E . E S PYTHON 25
INTEGRACIN OpenOffice

ores de las contizaciones, adems de paquetes que OpenOffice admite tienen una entrada de un men Ver Listado
cmo se actualizan cada 10 segundos. una estructura fija. Son ficheros ZIP [3].
Si creamos un grfico que use esos va- que contienen los ficheros con el La sintaxis del fichero parece bas-
lores, se actualizar con ellos. cdigo fuente, recursos (como im- tante complicada, cuando en realidad
Pero este es un programa externo genes) y un fichero de configuracin no es muy difcil de entender. Bsica-
estara bien que pudisemos hacer eso XML. mente decimos que queremos que nue-
pulsando un botn Los ficheros deben tener nombres stro componente se asocie con una
especiales. El fichero de configuracin entrada en el men Addons que est
Creamos un Componente UNO debe llamarse Addons.xcu y permite en Tools o Herramientas en castellano.
Los componentes UNO no son ms que asignar el cdigo fuente del paquete Nuestro componente tiene una ruta que
cdigo debidamente empaquetado. Los con el widget que deseemos, un botn, especificaremos despus y que es:

Listado 3: Addons.xcu
01 <?xml version=1.0 encoding=UTF-8?> 13 <value/>
02 <oor:node 14 <value xml:lang=en-US>Stock Market</value>
xmlns:oor=http://openoffice.org/2001/registry 15 <value xml:lang=es>Cotizacin en Bolsa</value>
03 xmlns:xs=http://www.w3.org/2001/XMLSchema 16 </prop>
04 oor:name=Addons 17 <prop oor:name=Target oor:type=xs:string>
oor:package=org.openoffice.Office>
18 <value>_self</value>
05 <node oor:name=AddonUI>
19 </prop>
06
20 <prop oor:name=ImageIdentifier
07 <node oor:name=AddonMenu> oor:type=xs:string>
08 <node oor:name= 21 <value>private:image/3216</value>
org.openoffice.comp.pyuno.linuxmagazine.Stock
oor:op=replace> 22 </prop>
09 <prop oor:name=URL oor:type=xs:string> 23
10 <value>service:org.openoffice.comp.pyuno. 24 </node>
linuxmagazine.Stock?insert</value> 25 </node>
11 </prop> 26 </node>
12 <prop oor:name=Title oor:type=xs:string> 27 </oor:node>

Listado 4: stock_comp.py
01 import uno 30 model = desktop.getCurrentComponent()
02 import unohelper 31
03 32 self.hojas = model.getSheets()
04 import random 33
05 import time 34 self.s1 = self.hojas.getByIndex(0)
06 import httplib 35
07 import csv 36 simbolos = [GOOG,MSFT,RHAT]
08 37 i = 0;
09 from com.sun.star.task import XJobExecutor 38
10 39 for s in simbolos:
11 def getSimbolo(simbolo): 40 self.actualiza(getSimbolo(s),i)
12 c = httplib.HTTPConnection (finance.yahoo.com) 41 i=i+1
13 c.request(GET,/d/quotes.csv?s=+simbolo+ 42
&f=sl1d1t1c1ohgv&e=.csv) 43 def actualiza(self, cotizacion, fila):
14 r = c.getresponse() 44 i=0
15 cad = r.read() 45 for entrada in cotizacion:
16 reader = csv.reader([cad]) 46 if (i == 0) or (i == 2) or (i ==3):
17 resultado = [] 47 self.s1.getCellByPosition(i,fila).setString
18 for row in reader: (entrada)
19 resultado = row 48 else:
20 return resultado 49 self.s1.getCellByPosition(i,fila).setValue
21 (float(entrada))
22 class StockJob( unohelper.Base, XJobExecutor ): 50
23 def __init__( self, ctx ): 51 i = i + 1
24 52
25 self.ctx = ctx 53 g_ImplementationHelper =
unohelper.ImplementationHelper()
26
54
27 def trigger( self, args ):
55 g_ImplementationHelper.addImplementation( StockJob,
28 desktop = org.openoffice.comp.pyuno.linuxmagazine.Stock,
self.ctx.ServiceManager.createInstanceWithContext (com.sun.star.task.Job,),)
(com.sun.star.frame.Desktop, self.ctx )
29

26 PYTHON W W W. L I N U X - M A G A Z I N E . E S
OpenOffice INTEGRACIN

org.openoffice.comp.pyuno.U Manejo de Paquetes en OpenOffice Tools/Herramientas, veremos cmo ha


linuxmagazine.Stock Ahora tenemos que generar nuestro aparecido al final un nuevo submen:
paquete UNO. Para ello necesitaremos Complementos (add-ons). Dentro
Esta ruta la hemos creado nosotros y el programa ZIP, gzip no nos vale, y del mismo aparecer una nueva
tenemos que tener cuidado de que sea crear un fichero: entrada llamada Cotizacin de
nica, por eso hemos incorporado li- Bolsa. Si la pulsamos aparecen los
nuxmagazine en ella ;). Definimos un $> zip stock.zipU datos de 3 compaas (Google,
ttulo, que puede estar en varios stock_comp.py Addons.xcu Microsoft y Redhat) del Nasdaq en
idiomas, y una imagen, que hemos updating: ...../Addons.xcuU nuestra hoja de clculo.
escogido de entre las que proporciona (deflated 59%)
OpenOffice. updating: ...../stock_comp.pyU Conclusin
El fichero con el cdigo fuente Python (deflated 57%) Python nos permite un uso nuevo de
en s se puede ver en el Listado 4. Ten- > algo tan trillado como puede ser un
emos un objeto llamado StockJob que paquete ofimtico. OpenOffice entero
ser el que se invocar en caso de pul- Este fichero debe ser integrado en es accesible desde Python; no es difcil
sar la entrada en el men. Ese objeto se OpenOffice, iremos al directorio donde imaginarse programas que podran
vincula a la ruta que vimos antes. Cada est instalado y ejecutaremos como facilitarnos mucho la vida y no son tan
vez que se pulse sobre la entrada del root: difciles de crear gracias a PyUNO. No
men se ejecutar el mtodo trigger, hemos explorado la posibilidad de
que descargar de Internet las cotiza- $> sudo ./unopkg addU actuar sobre un OpenOffice remoto por
ciones y las mostrar en la hoja de cl- stock.zip falta de espacio, pero es una nueva
culo. Es posible hacer que slo se > opcin que abre un camino para aplica-
muestre el men cuando arrancamos la ciones muy interesantes, como puede
hoja de clculo SCalc, pero por motivos Con esto concluye la instalacin del ser la edicin distribuida de documen-
de espacio no hemos puesto la restric- paqueteno ha sido tan difcil! tos o un uso ms creativo de la hoja de
cin. An as, si no estamos en una Cuando arranquemos de nuevo clculo.
hoja de clculo no suceder nada, sim- OpenOffice podremos seleccionar la Todo un mundo de posibilidades se
plemente no funcionar. hoja de clculo SCalc, y en el men abre ante nosotros gracias a Python.
INTEGRACIN Java

Yv
on
ne
Les
s-
123
RF.
com
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 progra-

mrais con Python. Por Pedro Orantes y Jos Mara Ruz

Muchos os preguntaris qu es Jython. que es capaz de generar Bytecode Java idea de su funcionamiento y disponer de
Bien, empecemos desde el principio. Un (aunque s necesitamos tener Java, la API de Java, disponible en la web de
grupo de programadores, Jim Hugunim obviamente). Aunque ciertamente no Sun [2], para saber qu queremos hacer
(su creador) y Guido van Rossum (perso- todo es positivo. Las aplicaciones desa- y cmo lo queremos hacer, pero la sin-
nalidad dentro del mundo de Python) rrolladas con Jython suelen ser bastante taxis y la forma de programar es muy
entre otros, decidieron que la simpleza y ms lentas que las desarrolladas con diferente. Programar en Java sin la API
la limpieza del cdigo de Python hara Java, algo de lo que se quejan muchos de Sun resulta, en la mayor parte de los
que programar en Java fuera perfecto, programadores, pero an as, la potencia casos, imposible. En cambio, para pro-
as que se pusieron manos a la obra y y rapidez de los ordenadores de hoy hace gramar en Jython s es necesario saber
crearon Jython. Jython es la implemen- que apenas se note la diferencia. programar en Python, as pues, damos
tacin de Python en la plataforma Java, Muchos os preguntareis si hace falta por sabido todo lo que hayis aprendido
combinando la potencia de los paquetes saber Java para programar Jython. En en los artculos anteriores de esta
de Java, como JavaBeans, con la facili- principio, no. S es conveniente tener revista.
dad y rapidez de desarrollo de aplicacio-
nes de Python. Recordad que desarrollar Listado 1: Tres Botones
una aplicacin es por lo menos dos veces
ms corto en Python que en Java. 01 import javax.swing as swing 15
02 import java.awt as awt 16 win.contentPane.add(pnl-
Jython posee las mismas caractersti- Botones)
03
cas de Python y adems posee la carac- 17 win.size=(300,300)
04 cuadroTexto =
terstica de poder trabajar con las libre- swing.JTextField(10) 18 win.pack()
ras de Java, de forma que, por ejemplo, 05 19 win.show()
podemos disponer del bonito swing de 06 def __init__(): 20
Java o utilizar JavaBeans e incluso pro- 07 win = swing.JFrame(Ejemplo con 21 def accion(event):
botones) 22 accionBoton = event.getAction-
gramar applets. En la Web de Jython en 08 acciones = [uno, dos, Command()
[1] aparecen algunos ejemplos de tres] 23 if accionBoton == uno:
applets desarrollados en Jython. Esto 09 24 cuadroTexto.setText(UNO)
hace de l un lenguaje muy potente, ya 10 pnlBotones = 25 elif accionBoton == dos:
swing.JPanel(awt.FlowLayout())
que nos ahorramos tiempo, lneas de 26 cuadroTexto.setText(DOS)
11
cdigo y resulta menos engorroso de leer 27 else:
12 pnlBotones.add(cuadroTexto)
que Java. Incluso podemos compilar el 28 cuadroTexto.setText(TRES)
13 for cadaBoton in acciones:
29 root = __init__()
cdigo para no tener la necesidad de 14 pnlBotones.add (swing.JBut-
instalar Jython antes de ejecutar nuestra ton(cadaBoton,actionPerformed=a
ccion))
aplicacin en cualquier ordenador, ya

28 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Java INTEGRACIN

vectores, entre otras cosas. Para el ejem- nador. Podemos usar el Java Runtime
plo de este artculo nos valdremos de Edition (j2re) o el Java Developers
una aplicacin externa ligeramente Kit(j2sdk), en su version 1.4.2 como
modificada (el cdigo original lo encon- mnimo, descargables desde [2]. Adems
traris en la web de xml.com en [3]) necesitamos instalar el intrprete de
desarrollada en Python, para que podis Jython disponible en [1], en su ltima
echarle un ojo al cdigo si os apetece, versin estable (la jython-2.1) y, por
desde la cual parsearemos el xml de un ltimo, para ejecutar nuestra aplicacin,
documento RSS para leer las ltimas deberemos tener instalado el intrprete
noticias de los blogs que ms frecuente- de Python (versin 2.3).
Figura 1: Nuestra utilidad busca-palabras en marcha. mente visitemos. Ms adelante expli- Una vez hemos descargado el intr-
caremos esto detalladamente. prete de Jython, debemos proceder a la
En este artculo aprenderemos a usar instalacin del mismo. Ejecutamos java
elementos bsicos de Java en Jython, Instalacin de Jython jython-21 y nos saldr el instalador
trabajaremos con swing, y usaremos Para trabajar con Jython, necesitamos (Figura 1), que nos pedir confirmar una
algunos objetos de Java como son los tener Java instalado en nuestro orde- serie de opciones y un directorio, y ya

Listado 2: JyRSS.py
001 #!/usr/bin/jython 042
002 043 for cadaBoton in acciones:
003 import javax.swing as swing 044 self.pnlBoton.add(swing.JButton(cadaBoton,
004 import java.lang as lang actionPerformed=self.accionMenu))
005 import java.awt as awt 045
006 import java.util as util 046 def menu(self):
007 import os 047 opciones = [Guardar]
008 048 self.menu = swing.JMenuBar()
009 class Lector: 049 archivo = swing.JMenu(Archivo)
010 def exit(self, event): 050 for eachOpcion in opciones:
011 lang.System.exit(0) 051 archivo.add(swing.JMenuItem(eachOpcion, action-
Performed=self.accionMenu))
012
052 self.menu.add(archivo)
013 def __init__(self):
053
014
054 def listaRSS(self):
015 self.vectorrss = util.Vector()
055 self.lstLista = swing.JList()
016 self.vectorurl = util.Vector()
056 self.jscplista = swing.JScrollPane(self.lstLista)
017 self.listaRSS()
057 self.jscplista.setSize(100,100)
018 self.listaNoticias()
058
019 self.pnlBotones()
059 def listaNoticias(self):
020 self.menu()
060 self.lstNoticias = swing.JEditorPane()
021 if os.path.exists(listarss.txt):
061 self.jscpNoticias = swing.JScrollPane(self.lst-
022 self.leeFicheroRss() Noticias)
023 self.win = swing.JFrame(JyRss, size=(300, 062
300),windowClosing=self.exit)
063 def leeFicheroRss(self):
024 self.win.setJMenuBar(self.menu)
064 f = open(listarss.txt,r)
025 self.win.contentPane.add(self.pnlBoton,awt.Bor-
derLayout.NORTH) 065 fu = open(listaurl.txt, r)
026 self.win.contentPane.add(self.jscplista, awt.Bor- 066 linea = f.readline()
derLayout.WEST) 067 lurl = fu.readline()
027 self.win.contentPane.add(self.jscpNoticias, 068 while linea:
awt.BorderLayout.CENTER) 069 self.vectorrss.add(linea)
028 self.win.setSize(600, 400) 070 self.vectorurl.add(lurl)
029 self.win.show() 071 linea = f.readline()
030 072 lurl = fu.readline()
031 def pnlBotones(self): 073 f.close()
032 self.pnlBoton = swing.JPanel(awt.FlowLayout()) 074 fu.close()
033 acciones = [Agregar,Borrar,Leer] 075 self.lstLista.setListData(self.vectorrss)
034 self.txtUrl = swing.JTextField(10) 076
035 lblNombre = swing.JLabel(Nombre) 077 def leeFicheroNoticias(self):
036 self.txtNombre = swing.JTextField(10) 078 fg = open(news.txt,r)
037 lblUrl = swing.JLabel(Url) 079 texto = fg.read()
038 self.pnlBoton.add(lblNombre) 080 fg.close()
039 self.pnlBoton.add(self.txtNombre) 081 self.lstNoticias.setText(texto)
040 self.pnlBoton.add(lblUrl) 082
041 self.pnlBoton.add(self.txtUrl) 083 def guardarFichero(self):

W W W. L I N U X - M A G A Z I N E . E S WWW.LINUX- MAGAZINE.ES PYTHON 29


INTEGRACIN Java

Type "copyright", "credits" orU


"license" for more information.
>>> print 'Hola Mundo'
Figura 2: Ejemplo con botones Swing. Hola Mundo
>>>
tendremos Jython instalado en nuestro
ordenador. Adicionalmente podemos Bien, vamos a empezar a ver algunos
enlazar los ejecutables de Jython a nues- ejemplitos en java. En primer lugar, qu Figura 3: Buscador desde la lnea de comandos.
tro directorio de binarios del sistema tal el tpico Hola mundo con swing? Este
(ln -s jython-2.1/jython /usr/bin/jython y ejemplo mostrar una ventana llamada 07 >>>
ln -s jython-2.1/jythonc /usr/bin/jythonc Hola Mundo, con un cuadro de texto. win.contentPane.add(texto)
en nuestro caso) para no tener que eje- Para cerrarla tendris que cerrar el intr- 08 >>> win.pack()
cutar los binarios desde el directorio prete con Control + C, ya que en el 09 >>> win.show()
donde lo tengamos instalado. Esto lti- ejemplo no implementamos la salida de 10 >>>
mo es recomendable, adems de que la aplicacin.
resulta mucho ms cmodo. Como podis ver resulta muy sencillo, a
01 $ jython la vez de que swing resulta muy agra-
Primeros Pasos 02 Jython 2.1 on java1.4.2_05 dable para la vista. Java incluye tambin
Bueno, ya est todo preparado en nues- (JIT: null) un sistema para cambiar la apariencia de
tro sistema. Es hora de ver cmo fun- 03 Type "copyright", "credits" la interfaz por si no os gusta la que
ciona Jython. Para empezar, podis or "license" arranca por defecto.
trastear un poco con el intrprete como for more information.
lo habis hecho con el de Python, y as 04 >>> import javax.swing as Programacin
podris ver que el funcionamiento es swing Al igual que en Python, para nuestras
idntico. 05 >>> win = swing.JFrame("Hola aplicaciones lo mas cmodo es hacer
mundo") que el intrprete ejecute un fichero (o
$ jython 06 >>> texto = varios) de cdigo. Jython no tiene
Jython 2.1 on java1.4.2_05U swing.JLabel("Hola ningn tipo de extensin establecida
(JIT: null) mundo") para los ficheros, y lo ms normal es
usar la misma que Python .py para tener
Listado 2: JyRSS.py (Cont.) nuestros ficheros de cdigo bien diferen-
ciados de los dems.
084 fg = open(listarss.txt,w) 108
085 furl = 109 self.lstLista.setListData
Para usar las libreras de Java, lo
open(listaurl.txt,w) (self.vectorrss) primero que tenemos que hacer es
086 j = self.vectorrss.size() 110 self.txtNombre.setText() importarlas al estilo de siempre de
087 i = 0 111 self.txtUrl.setText() Python. Como hemos visto en el ejemplo
088 while i<=j-1: 112 de antes, para importar las libreras de
089 texto = self.vectorrss.get(i) 113 elif self.accion == Leer:
swing basta con importar lo que nece-
090 fg.write(texto +\n) 114 item = self.lstLista.getSe-
lectedIndex() sitemos. Imaginad que necesitamos la
091 texto = self.vectorurl.get(i)
092 furl.write(texto +\n) 115 url = clase Vector, que est dentro del paquete
self.vectorurl.get(item) de utilidades, bien podemos hacer
093 i = i+1
116 os.system(python lrss.py + import java.util as util o bien directa-
094 fg.close() url)
095 furl.close() 117 self.leeFicheroNoticias() mente import java.util.vector as vector, y
096 118 ya tendremos acceso a esa clase. Para
097 def accionMenu(self, event): 119 elif self.accion == Borrar: utilizarla bastara con llamar a su cons-
098 self.accion = event.getAction- 120 itemborrar = tructor (os recuerdo que tengis siempre
Command() self.lstLista.getSelectedIn-
099 if self.accion == Agregar: dex() a mano la documentacin del API de
100 if self.txtNombre.getText() 121 self.vectorrss.remove(item- Java) con l, crearemos una ocurrencia
== : borrar) vector (en este caso), pepe = util.vec-
101 self.vectorrss.add(SIN NOM- 122 self.vectorurl.remove(item- tor() y una vez creada, podemos acceder
BRE\n) borrar)
102 else: 123 self.lstLista.setListData a cualquiera de sus mtodos directa-
103 self.vectorrss.add (self.vectorrss) mente en la ocurrencia que acabamos de
(self.txtNombre.getText()) 124 crear, pepe.add(Hola) que, para los
104 if self.txtUrl.getText() == 125 elif self.accion == Guardar: que no estn muy familiarizados con
: 126 self.guardarFichero() Java, aade un objeto de tipo String
105 self.vectorurl.add(SIN 127
URL\n) (cadena de caracteres) al vector.
128 root = Lector()
106 else: Cada ocurrencia de Java tiene el
107 self.vectorurl.add mismo funcionamiento en Jython. Es
(self.txtUrl.getText())
importante que tengis esto en cuenta.

30 PYTHON 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 pul-
sar 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, pasn-
dole 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 -JyRSS-


Puede que algunos de vosotros os preguntis qu es RSS.
RSS no es ms que un fichero .xml con una serie de etique-
tas. 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 men-
cionado al principio del artculo. Este cdigo ser el encar-
gado 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 cuan-
do 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 nom-
bres 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 Pongo los nombres de las clases y ciones) a la hora de programar.
pasaremos a la ocurrencia JList para paquetes de Java que se van a usar, para Actualmente no he encontrado an
que aada todos los nombres de los que os vayan sonando cuando vayis ninguno que sea exclusivamente para
sitios en pantalla. Usaremos el paquete leyendo el cdigo. As podris buscar desarrollar en Jython. Lo que s existe,
java.lang para implementar la funcin ms rpido en la API de Java y no ten- son plugins que instalamos en otros
de salir de la ventana al pulsar la x de dris problemas para encontrarlas. IDEs y que nos permiten trabajar con
nuestra aplicacin (no se implementa Vuelvo a hacer hincapi en que es nece- este lenguaje. Podemos encontrar dife-
por defecto). sario que lo tengis. rentes plugins para dos de los IDEs ms
De Python, haremos uso del paquete populares, uno para Netbeans (ver [4]),
os, desde el que nos valdremos de Mejoras para JyRSS que os podis bajar desde la aplicacin
os.path.exists() para comprobar si existe Es obvio que a este cdigo le faltan de actualizacin que lleva implementa-
el fichero de nombres al arrancar la apli- muchas cosas, adems de que debe tener da. Y luego tenis otro para Eclipse (ver
cacin y de os.system(), que ejecutar el varios bugs, como por ejemplo que al [5]) llamado Red Robin, que podis
script Python que leer las noticias. ste pulsar el botn Leer cuando no hay encontrar en [6]. Tanto en la web de
guardar las noticias en un fichero, que ninguna url y/o ningn nombre, Java Netbeans como en la web de Red
luego leer nuestra aplicacin. No os lanza una excepcin. Os animamos a que Robin, se explica cmo debemos insta-
preocupis si veis que tarda un poco, eso lo depuris. larlos.
es por dos motivos, tiene que descargar Os sugerimos que le hagis algunas
el fichero de Internet, y adems tiene mejoras, ya que de esta manera os Recursos
que parsearlo, y las libreras que utiliza servir para practicar con el cdigo que
[1] Pgina de Jython:
(minidom) son bastante lentas. os dejamos. Por ejemplo, hacer que el http://www.jython.org
Usaremos tambin las utilidades de cuadro JEditorPane os cambie la aparien-
[2] Descarga de Java:
escritura y lectura de ficheros (open(), cia del texto (negrita, cursiva, etc),
http://java.sun.com
write(), read(), etc) para manejarnos que cada vez que pulsis sobre un nom-
[3] Cdigo original del programa:
con ellos. Podemos ver el resultado en la bre en la JList lea la noticia directamente
http://www.xml.com/lpt/a/2002/12/18/
Figura 3. (deberis trabajar con el listener de dive-into-xml.html
Como vis, programar java con JList), aadir la opcin de guardar las
[4] Plugins de Netbean:
Jython no resulta para nada difcil, al noticias del sitio que ms os gusten,
http://www.netbeans.org
contrario, resulta muy cmodo, y desde etc
[5] IDE Eclipse:
luego mucho mas sencillo que Java. En
http://www.eclipse.org
el Listado 3 encontraris el cdigo Entornos de Programacin Jython
[6] Plugin Jython para Eclipse:
Python de la pequea aplicacin exter- Mucha gente prefiere trabajar con IDEs
http://home.tiscali.be/redrobin/jython/
na. (interfaz para el desarrollo de aplica-

Listado 3: lrss.py
01 from xml.dom import minidom 23
02 import urllib 24 def textOf(node):
03 25 return node and .join([child.data for child in
04 DEFAULT_NAMESPACES = \ node.childNodes]) or
05 (None, # RSS 0.91, 0.92, 0.93, 0.94, 2.0 26
06 http://purl.org/rss/1.0/, # RSS 1.0 27 if __name__ == __main__:
07 http://my.netscape.com/rdf/simple/0.9/ # RSS 0.90 28 import sys
08 ) 29 rssDocument = load(sys.argv[1])
09 DUBLIN_CORE = (http://purl.org/dc/elements/1.1/,) 30 fn = open(news.txt,w)
10 31 Noticia=
11 def load(rssURL): 32 for item in getElementsByTagName(rssDocument,
item):
12 return minidom.parse(urllib.urlopen(rssURL))
33 Noticia = Title: __ + textOf(first(item,
13 title))+ __\n
14 def getElementsByTagName(node, tagName, possible- 34 Noticia = Noticia + Link: \n + textOf(first(item,
Namespaces=DEFAULT_NAMESPACES): link))+ \n
15 for namespace in possibleNamespaces: 35 Noticia = Noticia + Description: \n\n +
16 children = node.getElementsByTagNameNS(namespace, textOf(first(item, description))+ \n
tagName) 36 Noticia = Noticia + \nDate: + textOf(first(item,
17 if len(children): return children date, DUBLIN_CORE))+ \n
18 return [] 37 Noticia = Noticia + \nAuthor: + textOf(first(item,
19 creator, DUBLIN_CORE))+ \n
20 def first(node, tagName, 38 Noticia = Noticia +
possibleNamespaces=DEFAULT_NAMESPACES): ---------------------------------------\n
21 children = getElementsByTagName(node, tagName, pos- 39 fn.write(Noticia)
sibleNamespaces) 40 fn.close()
22 return len(children) and children[0] or None

32 PYTHON 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 de campos o el arrastrar y soltar, que Esta fase ya casi ha pasado y ahora se
Feliz) es el nombre de la famosa eran imposibles en la Web. busca la sencillez, y en el momento justo
novela de Aldous Huxley, en ella nos Conforme avanzaba el tiempo, nume- surgi AJAX. Para ms informacin ver
muestra un mundo distinto y aterrador rosas empresas y personas proponan url [2] del cudro de Recursos.
pero que pareca, y parece, cada vez soluciones. La lista es interminable:
ms cercano. JavaScript, Java Applets, ActiveX, Tcl, Pero Qu es AJAX?
Nosotros no tenemos una visin tan VBScript, Macromedia Flash Muy buena pregunta. Lo cierto es que
pesimista del mundo, pero es probable Pero todas fallaban de uno u otra AJAX ha estado delante de nuestras nari-
que ese ttulo (que se podra traducir manera. En el caso de Java, para ejecutar ces todo el tiempo, esperando a que
literalmente por un nuevo y desafiante el Applet necesitabas tener instalado el alguna mente despierta lo redescubriese.
mundo) explique todo el revuelo que Java Runtime Environment, y la mayora El acrnimo AJAX se compone de las
est levantando AJAX. El trmino fue de los usuarios no saban ni qu era palabras Asynchronous JavaScript and
acuado por Jesse James Garrett en el aquello que se le peda. Lo mismo ocu- XML, trmino acuado por Jesse James
artculo [1] del cuadro de Recursos. rra con Flash. Garrett, y curiosamente su existencia se
Durante mucho tiempo, las GUIs, las Lo peor era que cuando estaba solu- debe a una de esas famosas violaciones
Interfaces Grficas de Usuario, han cionado el tema de la instalacin del de los estndares que suele realizar
dominado la informtica. La gente que software adecuado, los desarrolladores Microsoft con sus productos.
trabajaba en la Web siempre estaba creaban, y crean, pginas horribles lle- All por 1998, Microsoft introdujo den-
intentando convencer a todo el mundo nas de cosas movindose que distraen e tro de sus productos una librera que le
de que para la mayora de los programas, irritan. Se sentan impulsados a usar permita hacer consultas usando el pro-
una interfaz web bastaba. Pero los usua- hasta la ltima capacidad de las nuevas tocolo HTTP de manera autnoma y
rios estaban acostumbrados a ciertas herramientas y acababan generando asncrona. Cuando tu navegador accede
caractersticas, como el auto-completado monstruosidades. a una pgina y sta contiene cdigo

W W W. L I N U X - M A G A Z I N E . E S 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 eje-
cucin respondiendo a eventos, tenemos
entre manos la posibilidad de traer infor-
macin al navegador sin recargar la pgi-
na.
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 modifi-
car el contenido de la pgina en tiempo Figura 1: Esquema de nuestra aplicacin AJAX con todos sus compenentes.
real usando el denominado rbol DOM.
Y por si fuese poco, cuando AJAX fue GUI tradicional. Y todo esto sin necesi- Los 5 Ingredientes
definido, los programadores comenzaron dad de plugins ni instalaciones; toda esta Los cinco ingredientes necesarios para
a usar protocolos XML para comunicarse tecnologa est en nuestros navegadores elaborar nuestro producto son CSS,
con los servidores. esperando ser usada. Javascript, HTML, XML y Python, como
Qu quiere decir esto? Pues que aparecen en la figura 1, y cada uno tiene
ahora, con AJAX, podemos cargar una Cmo Encaja Python? su funcin en esta obra.
pgina y, sin tener que recargarla, traer- Pues vamos a realizar un pequeo servi- HTML es la base sobre la que vamos a
nos informacin, modificar la pgina en dor de contenidos en Python que pueda trabajar, y as definimos una pgina web
tiempo real e interactuar con servidores ser consultado usando AJAX. Crearemos en la que todo ocurrir. De hecho, con el
remotos usando protocolos XML. una web con algo de cdigo Javascript, paso del tiempo el propio HTML ha aca-
Bsicamente, una vez cargada la pgi- que a intervalos acceder a nuestro ser- bado convirtindose en una especie de
na web tenemos entre manos todas las vidor Python y modificar el aspecto de plantilla donde campan a sus anchas
posibilidades de programacin de una la pgina web. CSS y Javascript.

Listado 1: server.py
01 #!/usr/local/bin/python 29 else:
02 30 self.envia_fichero(,404.html)
03 import BaseHTTPServer 31
04 import os 32 def envia_fichero(self,ruta,fichero):
05 import cgi 33 # No usamos ruta, pero as simplificamos el cdigo
06 34 p = Pagina(fichero)
07 class AJAXHTTPRequestHandler (BaseHTTPServer. 35 self.enviar_respuesta(p.tipo(), p.contenido())
BaseHTTPRequestHandler): 36
08 37 def envia_comando(self,ruta,comando):
09 Responde a peticiones HTTP 38 c = Comando(comando)
10 39 self.enviar_respuesta(c.tipo(), c.contenido())
11 def do_GET(self): 40
12 Gestiona los GET 41 def enviar_respuesta(self, tipo, contenido):
13 42 self.enviar_cabecera(tipo)
14 acciones = { 43 self.wfile.write(contenido)
15 / : [envia_fichero,index.html], 44
16 /ps.xml : [envia_comando, ps afx], 45 def enviar_cabecera(self, tipo):
17 /df.xml: [envia_comando, df], 46 self.send_response(200)
18 /who.xml: [envia_comando,who], 47 self.send_header(Content-type,text/ + tipo)
19 /uname.xml: [envia_comando,uname -a]} 48 self.end_headers()
20 49
21 if self.path in acciones.keys(): 50 class Pagina:
22 accion = acciones[self.path] 51 def __init__(self,nombre):
23 (getattr(self,accion[0]))(self.path,accion[1]) 52 self.nombre = nombre
24 else: 53 self.texto =
25 if (self.path[-3:] == .js or 54 fichero = file(self.nombre)
26 self.path[-4:] == .css): 55 self.texto = fichero.read()
27 self.envia_fichero(,self.path[1:]) 56 fichero.close()
28 57

34 PYTHON 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): 79 if not self.xml:
59 return self.texto 80 self.xml = <?xml version=\1.0\ ?>
60 81 self.xml += <salida>
61 def tipo(self): 82 linea = self.tuberia.readline()[:-1] # para quitar
62 tipo = html el \n
63 ext = self.nombre[-4:] 83 while linea:
64 if (ext == html): 84 self.xml += <linea> + cgi.escape(linea) +
</linea>
65 tipo = html
85 linea = self.tuberia.readline()[:-1]
66 elif (ext == .xml):
86 self.xml += </salida>
67 tipo = xml
87 return self.xml
68 elif (ext == .css):
88
69 tipo = css
89 def tipo(self):
70 return tipo
90 return xml
71
91
72 class Comando:
92 def test(HandlerClass = AJAXHTTPRequestHandler,
73 def __init__(self,comando):
93 ServerClass = BaseHTTPServer.HTTPServer):
74 self.tuberia = os.popen(comando)
94 BaseHTTPServer.test(HandlerClass, ServerClass)
75 self.xml =
95
76
96 if __name__ == __main__:
77 def contenido(self):
97 test()
78 # fichero XML

CSS nos permite otorgar propiedades sin tener que cambiar ninguno de los AJAXHttpRequestHandler, ver Listado 1
visuales a los elementos de HTML. otros elementos. (todos los listados de este artculo pue-
Javascript es el encargado de actuar den descargarse del articulo [4] del cua-
en la mquina cliente, en el navegador, La Parte de Python: dro de Recursos.
y puede modificar tanto el HTML como BaseHTTPRequest Un servidor HTTP recibe distintos
las propiedades visuales que CSS defi- Python dispone en sus libreras estndar tipos de comandos, pero el que nos inte-
ne. Con la llamada XMLHttpResponse de muchos esqueletos para distintos resa es el comando GET. El cual se usa
sus atribuciones se han disparado. tipos de servidores. Entre ellos encontra- para solicitar informacin al servidor.
Ahora se ve como un lenguaje de pro- mos BaseHttpRequest. Esta clase nos Cada vez que se realice una peticin GET
gramacin de pleno derecho. En los permite construir servidores HTTP sin se invocar el mtodo do_GET de
prximos aos puede que adquiera excesivo esfuerzo, as que la empleare- BaseHTTPRequestHandler, as que lo
mucha ms importancia de la que ha mos. vamos a redefinir en nuestra clase.
tenido hasta ahora. Pero no debemos usarla directamente, Cuando se invoque do_GET, en la
XML es el nuevo lenguaje estndar de sino a travs de la clase BaseHttp variable de instancia self.path se encuen-
intercambio de informacin. RequestHandler, que es la encargada de tra la ruta solicitada por el cliente.
Prcticamente cualquier lenguaje dispo- gestionar los eventos que se suceden en Nosotros contrastaremos esta ruta contra
ne ya de libreras para generar y analizar el servidor. Por tanto, heredaremos de las que aceptamos. Si no se encuentra
documentos XML. Dentro del mundillo, ella y crearemos una clase llamada entre ellas, devolveremos la clebre pgi-
AJAX se ha convertido en el estndar
para el intercambio de informacin con Listado 2: Fichero index.html
el servidor y para la serializacin de
01 <html> 14 <input type=button
objetos con protocolos como JSON. name=button value=Disco
02 <head>
Y, como no, Python. En nuestro caso 03 <title>Pruebas con AJAX</title> 15 onclick=javascript:haz
se va a encargar tanto de realizar las Peticion(df.xml); />
04 <link rel=stylesheet
href=estilo.css 16 <input type=button
tareas de servidor HTTP, como de reco- name=button value=Usuarios
type=text/css />
lectar informacin importante y confec- 17 onclick=javascript:haz
05 <script language=Javascript
cionar con ella ficheros XML. Peticion(who.xml); />
06 src=ajax.js>
Tenemos que compenetrar todos estos 18 <input type=button
07 </script> name=button value=Mquina
elementos para realizar nuestro proyec- 08 </head> 19 onclick=javascript:haz
to. El objetivo es que los componentes 09 <body> Peticion(uname.xml); />
HTML, Javascript, CSS y XML sean tan 10 <div id=documento> 20 <div id=contenedor>
estticos como sea posible, debido a que 11 <h3 id=titulo>Informacin 21 <div id=datos></div>
del sistema</h3> 22 </div>
todos ellos interactan con el usuario.
12 <input type=button 23 </div>
Es en el servidor Python donde debe- name=button value=Procesos
24 </body>
mos aportar la flexibilidad necesaria 13 onclick=javascript:haz
Peticion(ps.xml); /> 25 </html>
como para aadir nuevas caractersticas

W W W. L I N U X - M A G A Z I N E . E S PYTHON 35
INTEGRACIN Ajax

<?xml version="1.0"?> <ruta> : [<mtodo_a_invocar>,U


<salida> <comando_a_ejecutar>],
<linea>linea de salida</linea>
... Cuando se gestiona el comando HTTP
<linea>linea de salida</linea> GET, se busca en este diccionario la ruta.
... En caso de que est presente, se ejecuta-
<linea>linea de salida</linea> r el mtodo almacenado usando como
</salida> parmetros la ruta y el comando. Esto
Figura 2: La clase BaseHTTPRequest genera nos da gran flexibilidad, aadir un nuevo
una entrada por comando. Por lo que generaremos el fichero XML servicio consiste en introducir una nueva
a mano, sin hacer uso de libreras. De linea de cdigo.
na 404, indicando que la pgina solicita- esta manera, cuando el cliente solicite el Existe un detalle importante, todo fichero
da no existe. La informacin se devuelve fichero ps.xml, nuestro servidor ejecuta- devuelto usando HTTP debe tener una
usando el mtodo self.wfile.write(), lo r el comando ps afx, crear el fichero cabecera con una serie de lneas con forma-
que en l escribamos ser devuelto al ps.xml con la salida del comando y se lo to llave: valor que dan informacin al clien-
cliente que realiz la peticin. enviar al cliente. te, el navegador, sobre el fichero devuelto.
Adems del fichero index.html, ofrece- Debido a problemas de espacio hemos deci-
remos una serie de servicios en forma de Definicin de Servicios dido devolver slo el formato del fichero.
ficheros XML. Estos servicios consistirn Para permitir que la definicin de servicios Para ello se invoca el mtodo envia_cabece-
en la ejecucin de un comando de siste- sea lo ms simple posible, basta con intro- ra desde envia_respuesta antes de enviar el
ma, y la conversin de su salida en un ducir una nueva entrada en el diccionario fichero en s. Es de esta manera como el
fichero XML. El formato del fichero ser acciones de la clase AJAX HTTP navegador determina el tipo de fichero que
muy sencillo: RequestHandler con la siguiente estructura: recibe y sus caractersticas.

Listado 3: Fichero ajax.js.


01 // GLOBALES 35 function modificaContenido() {
02 var http_request = false; 36 if (http_request.readyState == 4) {
03 37 if (http_request.status == 200) {
04 function hazPeticion(url) { 38 vaciaContenido();
05 http_request = false; 39
06 http_request= new XMLHttpRequest(); 40 var xmldoc = http_request.responseXML;
07 if (http_request.overrideMimeType) { 41 var root =
08 http_request.overrideMimeType(text/xml); xmldoc.getElementsByTagName(salida).item(0);
09 } 42
10 43 var fondo = 0;
11 if (!http_request) { 44
12 alert(Error al crear la instancia de XMLHttp 45 for(var i = 0; i < root.childNodes.length; i++){
Request.); 46 var nodo = root.childNodes.item(i);
13 return false; 47
14 } 48 var contenedor = document.getElementById
15 (contenedor);
16 // Esto es un callback, que se dispara al terminar de 49 var p = document.createElement(p);
17 // descargar el fichero xml. 50
18 http_request.onreadystatechange = modifica 51 // Truco para los colores ;)
Contenido; 52 if (fondo == 0) {
19 http_request.open(GET, url, true); 53 p.setAttribute(id,linea0);}
20 http_request.send(null); 54 else {
21 } 55 p.setAttribute(id,linea1);
22 // Elimina todo elemento con id linea 56 }
23 function vaciaContenido(){ 57 fondo = 1 - fondo;
24 var d = document.getElementById(contenedor); 58
25 while(document.getElementById(linea0) || 59 var titulo = document.getElementById(datos);
document.getElementById(linea1)){ 60 p.textContent = nodo.firstChild.data;
26 nodo = document.getElementById(linea0); 61
27 if (! nodo){ 62 contenedor.insertBefore(p,titulo);
28 nodo = document.getElementById(linea1); 63 }
29 } 64
30 var nodo_basura = d.removeChild(nodo); 65 } else {
31 } 66 alert(Hubo un problema con la peticin.);
32 } 67 }
33 68 }
34 // Carga el resultado del XML 69 }

36 PYTHON 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 apare-


ciendo 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 pri-
mera, 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 mis-
mos. 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 per-
mite 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 cdi-
go 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 com-
plicado. 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 Nos referimos al ahora famoso no est disponible) y entonces obtiene
Javascript, un fichero CSS y muestra un XMLHttpRequest. el documento XML del objeto que gestio-
ttulo junto a unos cuantos botones. Si vemos el cdigo del Listado 3, vere- naba la conexin.
Estos botones invocan acciones en mos un lenguaje que quiz nos recuerda Pero antes invoca a vaciaContenido(),
Javascript. a Java. En realidad no tienen absoluta- que localiza toda etiqueta con las ids
Debemos fijarnos especialmente en el mente nada que ver, aparte del nombre. linea0 o linea1 y las elimina de la
uso del atributo id en numerosas eti- En nuestro ejemplo observamos tres fun- pgina. Hacemos esto porque a conti-
quetas HTML. Gracias a estos ids ciones: nuacin las volvemos a introducir en la
podremos manipularlas mediante hazPeticion() pgina, pero esta vez con los datos fres-
Javascript. modificaContenido() cos del servidor.
vaciaContenido() No queremos entrar en los detalles,
Javascript, desde Otra Perspectiva Javascript, adems de muchas de las ya que, en teora, esto es un artculo
Mucha gente ha tenido extraos encuen- caractersticas presentes en otros lengua- sobre Python, no Javascript, pero bsi-
tros con este lenguaje de programacin. jes (y algunas ausentes) dispone de una camente esto es lo que hace el fichero
Es raro y, hasta hace no demasiado, no coleccin de objetos que le permiten rea- ajax.js.
muy til. Te permita modificar colores lizar operaciones. A da de hoy los ms El fichero estilo.c, que aparece en el
en pginas web o poner insidiosos ban- importantes son: listado 4, simplemente configura los
ners. Por no hablar de las famosas venta- Manipulacin DOM colores y caractersticas de algunas de
nas emergentes. Manipulacin XML las etiquetas para que el aspecto mejore.
Esto ha hecho que se haya ganado una XMLHTTP Y se acab, aqu tenemos nuestra aplica-
fama muy mala, tal es as que casi todos DOM permite a Javascript manipular, en cin AJAX.
tenemos restricciones en nuestro navega- tiempo real, el contenido de la pgina El resultado final ser el que vemos en
dor en torno a qu acciones puede o no web. Puede aadir, modificar o quitar la figura 3. Cuando pulsemos cualquiera
realizar Javascript. Lo ms normal es etiquetas y atributos, por lo que pode- de los botones, se cargar la salida de
que tengamos uno de esos famosos blo- mos operar sobre el documento de cual- texto de la ejecucin asociada a cada uno
queadores de popups. quier forma posible. Javascript puede de ellos en pantalla, pero sin recargar la
Pero Javascript se ha reinsertado en la manipular un fichero XML de igual pgina. Si queremos aadir un nuevo
sociedad de los programadores por la forma que hace DOM. comando, slo tenemos que introducir la
puerta grande gracias a un solo objeto. Y la gran novedad, Javascript puede lnea correspondiente en acciones en ser-
realizar conexiones ASNCRONAS con el ver.py y aadir un nuevo botn como los
Listado 4: Fichero estilo.css servidor. Y resalto en mayscula la pala- que ya existen en index.html.
bra ASNCRONAS porque ah est la
01 #documento{
02 margin-left: 100px; clave. Conclusin
03 } Esto significa que podemos hace cone- Es tan complicado eso de AJAX? Por
04 xiones, traernos documentos XML del supuesto que no! Lo que ocurre es que
05 servidor y realizar operaciones DOM o es una palabra que se est convirtiendo
06 #titulo{ de cualquier otro tipo, cuando quera- en un mito, pero no deja de ser una astu-
07 text-decoration: underline; mos! ta combinacin de programacin en el
08 } El usuario carga su pgina web, y servidor y cliente adems del uso inten-
09
una vez cargada, sin necesidad de sivo de XMLHttpRequest.
10
recargarla, podemos modificarla a Poco a poco AJAX est poblando todas
11
12 #linea0 {
nuestro antojo en base a informacin las pginas webs y la mayora de los
13 margin: 0px; que podemos pedir al servidor en cual- currculos vitae. Quin sabe, lo mismo
14 padding-left: 20px; quier momento. dentro de dos aos esa palabra tenga
15 background: #e0e0e0; Volviendo a nuestras funciones, la fun- tanto poder como otra palabra de cuatro
16 font-family: monospace; cin hazPeticion() recibe una url, crea el letras: J2EE.
17 } objeto XMLHttpRequest y despus de
18 algunas comprobaciones, asigna una Recursos
19 #linea1 { funcin para que sea invocada cuando el
20 margin: 0px; [1] Jesse James Garret define AJAX:
fichero que esa url especifica sea com- http://adaptivepath.com/ideas/
21 padding-left: 20px;
pletamente descargado. ajax-new-approach-web-applications
22 font-family: monospace;
23 } Esto significa que mientras leemos
[2] Explicacin de AJAX en Wikipedia:
24 nuestra web, Javascript estar bajando http://en.wikipedia.org/wiki/AJAX
25 #contenedor{ un fichero y, cuando finalice, llamar a
[3] Sarissa: http://sarissa.sourceforge.net/
26 border-style: dashed; la funcin modificaContenido.
doc/
27 border-width: 1px; Y qu hace esta funcin? Comprueba
28 width: 600px; [4] Listados de este artculo: http://www.
el estado de la peticin, (el estado 200 el
29 border-color: black; linux-magazine.es/Magazine/
de todo correcto y el 404 el de lo sen- Downloads/Especiales/06_Python
30 }
timos mucho, pero el fichero solicitado

38 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.


En el ao 2003, IronPython realmente ha contribuido para
Jim Hugunin ley que Mono se convierta en un mejor run-
que la plataforma time.
de Microsoft .NET Vamos a echar un buen vistazo a las posi-
no era adecuada para bilidades de IronPython y veremos cmo
la creacin de lenguajes nos permite explotar el poder de las libreras
dinmicos (un lenguaje .NET, pero siempre desde nuestro lenguaje
dinmico es aquel al que favorito.
no debes decirle qu tipo
Preparativos
Eric

de variable vas a usar) Figura 1: Hola Mundo con WinForms.


Isse

Hugunin se extra Para poder hacer uso de IronPython necesi-


l

bastante del tamos instalarlo. IronPython es una imple- todo aquel que haya trabajado mucho en la
e -

comentario porque mentacin de Python sobre .NET, por lo que consola de Linux, porque Control z sirve
123R

ya haba creado un necesitamos una implementacin de .NET para mandar a un programa a background.
F

intrprete de para Linux. La secuencia sera:


Python, Jython, Mono es la implementacin libre de .NET
para la mquina ms famosa. Como vimos antes, Miguel de 01 IronPython 1.0 (1.0) on
virtual de Java. Este intrprete consigui Icaza est usando IronPython como banco .NET 2.0.50727.42
cierto reconocimiento, y prueba de ello de pruebas para Mono. Esto nos asegura 02 Copyright (c) Microsoft
es el artculo sobre Jython que aparece que IronPython funcionar bastante bien Corporation. All rights
en el nmero 7 de esta misma revista. sobre Mono. Tenemos que instalar IronPy- reserved.
thon y Mono. 03 >>> quit
Python sobre .NET Falta un componente en la ecuacin: libg- 04 Use Ctrl-Z plus Return to exit
As que ni corto ni perezoso Jim se tom el diplus (ver Recurso [4]). En este artculo 05 >>> exit
comentario como algo personal y se puso haremos uso de la interfaz grfica de usua- 06 Use Ctrl-Z plus Return to exit
manos a la obra. Como resultado de su rio que .NET pone a disposicin de sus des- 07 >>>
esfuerzo, el 5 de Septiembre de 2006 apare- arrolladores. Este sistema, llamado Win- 08 Suspended
ci la versin 1.0 de IronPython [1), nombre Forms, ha sido reimplementado en forma de 09 >
que dio a su intrprete [2]. software libre en la librera libgdiplus. Sin 10 >
Antes de la aparicin de la versin 1.0, ella no podremos hacer uso de WinForms, y
IronPython ya haba llamado la atencin de por ello necesitamos instalarla. Jim tuvo que crear este sistema para que
gran cantidad de desarrolladores. La posibi- Un detalle tambin algo molesto, cmo funcionara tanto en Windows como en
lidad de crear aplicaciones grficas o web se sale del intrprete?
multiplataforma aprovechando los recursos La verdad es que es Listado 1: Mostrar el Contenido de un Fichero de Texto
de .NET era demasiado interesante para algo rebuscado. No 01 import clr
dejarla escapar. funcionan ni exit ni 02
Incluso Miguel de Icaza, de fama mundial quit. Cuando ejecute- 03 from System.IO import *
gracias a Gnome y Mono, reconoci la mos cualquiera de los 04
importancia de IronPython para Mono en el dos, seremos infor- 05 fichero = File.OpenText(mifichero.txt)
artculo que aparece referenciado en el mados de que debe- 06
Recurso [3]: Prcticamente cada nueva ver- mos pulsar Control z 07 linea = fichero.ReadLine()
sin de IronPython ha expuesto las limita- para salir del intr- 08
09 while s:
ciones de nuestro runtime (Mono), nuestras prete. Esto puede
10 print linea
libreras de clases o nuestros compiladores. resultar curioso para

W W W. L I N U X - M A G A Z I N E . E S PYTHON 39
INTEGRACIN .Net

Copyright (c) MicrosoftU Libreras .NET


Corporation. All rightsU Hasta el momento no hemos hecho nada
reserved. especial, es hora de comenzar con lo intere-
>>> sante. IronPython est escrito en C# sobre
.NET. Una de las virtudes de .NET es que
Microsoft Corporation? Tranquilos, tran- una vez que ha sido compilado a su len-
quilos! IronPython posee una licencia casi guaje intermedio, puedes hacer uso de cual-
libre, parte del Microsofts Shared Source. quier librera escrita en cualquier lenguaje
Aunque su licencia no est aprobada por para .NET.
OSI, Jim dice que la licencia que lo cubre IronPython tiene a su disposicin todas
sigue todas las normas de OSI (ver Recurso las libreras del proyecto Mono. Esto incluye
Figura 2: Visor de texto. [5]). la librera para programacin de aplicacio-
Vayamos al grano, nuestro hola mundo nes grficas WinForms, las libreras para
Linux. Si ya tienes todos estos paquetes ins- es reconocible: programacin web ASP.NET, estructuras de
talados en tu distribucin, podemos pasar al datos, libreras de cifrado, a las que hay
siguiente paso el infame Hola Mundo. >>> print Hola Mundo que sumar aqullas que ha incorporado el
Hola Mundo proyecto Mono, como por ejemplo las que
Hola Mundo Consola >>> nos permiten empotrar el motor Gecko (de
La primera sorpresa de IronPython es que Firefox) en una aplicacin grfica, de forma
realmente es casi igual a Python pero ms Como el lector podr apreciar, no ha cam- que podemos crear un navegador web con
lento. Para ejecutar un programa .NET en biado nada. IronPython mantiene una gran muy poco cdigo.
Linux debemos emplear el comando mono compatibilidad con Python. Pero no todo el Veamos un ejemplo de uso. Vamos a abrir
que lo ejecuta dentro de la mquina virtual monte es organo. Debido a que se ejecuta un fichero y a mostrarlo e imprimirlo en el
de .NET. sobre .NET, IronPython no puede acceder a terminal, ver Listado 1. No es muy compli-
libreras de Python escritas en C. Esto es un cado verdad? Lo ms importante de este
> mono miprograma.exe problema porque, por ejemplo, la librera ejemplo es la sentencia:
sys est escrita en C.
Probablemente, el comando ironpython de Otro detalle bastante molesto es que el import clr
vuestra distribucin sea en realidad un script intrprete de IronPython no tiene historia, ni
shell que ejecuta el intrprete (llamado autocompletacin, ni nada de nada. Esto que nos permite hacer uso de todas las libre-
ipy.exe) empleando mono. El resultado ser: puede poner a ms de uno de los nervios, ras de .NET.
yo al menos me puse, por lo que es reco-
IronPython 1.0 (1.0) onU mendable trabajar en un fichero y ejecutarlo Hola Mundo Winforms
.NET 2.0.50727.42 con IronPython. Hasta ahora todo lo que hemos hecho era
fcilmente realizable con Python, a partir
Listado 2: Hola Mundo con WinForms de este momento observaremos cambios.
Vamos a realizar el mismo Hola Mundo
01 import clr 17 self.panel1 = Panel()
pero empleando WinForms. Microsoft ha
02 clr.AddReference(System. 18 self.panel1.Location = Point
Drawing) (0,0) simplificado bastante el desarrollo de apli-
03 clr.AddReference(System. 19 self.panel1.Width = self.Width caciones grficas con esta librera. IronPy-
Windows.Forms) 20 self.panel1.Height = thon lo ha simplificado an ms.
04 self.Height WinForms, ver Recurso [6], funciona de
05 from System.Drawing import 21
Color, Point
forma similar a como lo hacen otras libre-
22 self.generaSaludo()
06 from System.Windows.Forms import ras grficas. Creas una ventana, dentro de
23
(Application, BorderStyle, But- la cual creas un panel, dentro del cual se
ton, Form, FormBorderStyle, 24
self.panel1.Controls.Add(self.l pueden disponer widgets. Es muy parecido
Label, Panel, Screen)
abel1) a las muecas rusas Matroskas. Veamos el
07
25 self.Controls.Add(self.panel1) cdigo en el Listado 2 y el resultado en la
08 class HolaMundo(Form):
26 Figura 1.
09 def __init__ (self):
27 def generaSaludo(self): Comencemos por el principio. Una vez
10 self.Text = Hola Linux
Magazine 28 self.label1 = Label() importado clr, hemos de hacer algo que no
11 self.FormBorderStyle = 29 self.label1.Text = Hola
lectores de Linux Magazine
es normal en Python: debemos aadir refe-
FormBorderStyle.FixedDialog
30 self.label1.Location = rencias a las libreras de .NET que vamos a
12
Point(20,20) utilizar. Para ello utilizamos el mtodo
13 pantalla = Screen.Get-
WorkingArea(self) 31 self.label1.Height = 25 clr.AddReference() con el nombre de la libre-
14 self.Height = pantalla.Height / 32 self.label1.Width = self.Width ra que vamos a usar.
5 33 Winforms hace uso de System.Drawing
15 self.Width = pantalla.Width / 5 34 form = HolaMundo() y de System.Windows.Forms. Estas libre-
16 35 Application.Run(form) ras contienen todos los widgets necesarios

40 PYTHON W W W. L I N U X - M A G A Z I N E . E S
.Net INTEGRACIN

para crear aplicaciones grficas. Una vez Calculamos el tamao de la ventana en proceso es repetitivo: texto de etiqueta, posi-
que aadamos las referencias a estas libre- base al de la pantalla. Conseguimos los cin, altura y anchura.
ras, podemos usarlas como cualquier libre- datos de la pantalla mediante el mtodo Finalmente, aadimos la etiqueta al panel
ra de Python. Screen.GetWorkingArea(), y hacemos que y el panel a la ventana (esto ltimo
Importamos todos los widgets necesa- nuestra ventana tenga un quinto de la mediante self.Controls.Add()). Con estas
rios: Application, BorderStyle, Button altura (Height) y ancho (Width) de la ltimas sentencias terminamos de definir
Para hacer referencias a posiciones en la pantalla. Podramos haber indicado el nuestra clase.
pantalla emplearemos la clase Point de tamao mediante un nmero, digamos Para poder hacer uso de ella creamos una
System.Drawing, expresando la posicin en 100 pixels. instancia de HolaMundo y se la pasamos a
pixels. Creamos un panel que pasar a contener Application.Run(), que es un bucle sin fin
Con todas las libreras cargadas, pode- todos los widgets que utilicemos. De nuevo que se dedicar a gestionar los eventos
mos comenzar inicializando nuestra clase ajustamos su altura y anchura, as como su sobre la ventana.
HolaMundo, que representa la ventana de posicin dentro de la ventana. Como quere- La explicacin es bastante ms larga
la aplicacin. Comenzamos dndole mos que ocupe toda la superficie de la ven- que el texto, pero el lector se habr dado
ttulo, con self.Text, a la ventana. Defini- tana lo posicionamos en (0,0), y le damos el cuenta de lo simple que es realmente el
mos el tipo de ventana que utilizaremos mismo ancho y la misma altura que la ven- proceso. Incluso llega a ser aburrido por
con FormBorderStyle indicando que ser tana. He aadido un mtodo, para simplifi- repetitivo.
fijo, nuestra ventana no se podr redi- car el cdigo, que genera una etiqueta Pero hemos logrado nuestro objetivo, rea-
mensionar. donde realizamos el saludo. De nuevo el lizar una aplicacin grfica con un mnimo

Listado 3: Visor de Texto Simple


01 import clr (self.label1) 61 self.boton1.Name= Botn 1
02 clr.AddReference(System. 30 self.panel1.Controls.Add 62 self.boton1.Text = Abrir
Drawing) (self.label2) fichero
03 clr.AddReference(System. 31 self.panel1.Controls.Add 63 self.boton1.Location =
Windows.Forms) (self.boton1) Point(20,80)
04 32 self.panel1.Controls.Add 64 self.boton1.Height = 25
05 from System.IO import * (self.areaTexto) 65 self.boton1.Width = 100
06 from System.Drawing import Color, 33 66 self.boton1.Click +=
Point 34 self.Controls.Add( self.abreFichero
07 from System.Windows.Forms import self.panel1) 67
(Application, BorderStyle, 35 68 def abreFichero(self, sender,
Button, Form, FormBorderStyle, 36 def generaAreaTexto(self): event):
Label, Panel, Screen, OpenFile
Dialog, DialogResult, TextBox, 37 texto = TextBox() 69 color = OpenFileDialog()
ScrollBars) 38 texto.Height = self.Height / 2 70 color.Filter = Ficheros txt
08 39 texto.Width = self.Width - 30 # (*.txt)|*.txt
09 class LectorTXT(Form): para que no se salga 71 color.Title = Selecciona un
40 texto.Location = Point(20,110) fichero de texto
10 def __init__ (self):
41 texto.Multiline = True 72
11 self.Text = Visor de texto Linux
Magazine 42 texto.ScrollBars = 73 nombre =
12 self.FormBorderStyle = ScrollBars.Vertical 74
FormBorderStyle.FixedDialog 43 self.areaTexto = texto 75 if (color.ShowDialog() ==
13 44 DialogResult.OK ):
14 pantalla = Screen. 45 def generaLabel1(self): 76 nombre = color.FileName
GetWorkingArea(self) 46 self.label1 = Label() 77 self.label2.Text = Fichero
15 self.Height = 300 seleccionado: + nombre
47 self.label1.Text = Lector de
16 self.Width = 400 ficheros de texto Linux Magazine 78 # cargamos el texto
17 48 self.label1.Location = 79 fichero = File.OpenText
Point(20,20) (nombre)
18 self.panel1 = Panel()
49 self.label1.Height = 25 80 texto =
19 self.panel1.Location = Point
(0,0) 50 self.label1.Width = self.Width 81
20 self.panel1.Width = self.Width 51 82 s = fichero.ReadLine()
21 self.panel1.Height = 52 def generaLabel2(self): 83 while s :
self.Height 53 self.label2 = Label() 84 texto += s
22 self.panel1.BorderStyle = 54 self.label2.Text = Fichero 85 s = fichero.ReadLine()
BorderStyle.FixedSingle seleccionado: ?? 86
23 55 self.label2.Location = 87 self.areaTexto.Text = texto
24 self.generaLabel1() Point(20,50) 88
25 self.generaLabel2() 56 self.label2.Height = 25 89 form = LectorTXT()
26 self.generaBoton1() 57 self.label2.Width = self.Width 90
27 self.generaAreaTexto() 58 91 Application.Run(form)
28 59 def generaBoton1(self):
29 self.panel1.Controls.Add 60 self.boton1 = Button ()

W W W. L I N U X - M A G A Z I N E . E S PYTHON 41
INTEGRACIN .Net

Hemos definido un botn, parecido al que vimos en el Listado 1 para


y queremos que cuando cargar el texto en una variable. Cuando lo
se pulse se ejecute el tengamos, cargaremos el texto en la variable
mtodo abreFichero. El self.areaTexto.Text, lo que provocar que se
evento que se dispara modifique el contenido del rea de texto.
cuando pulsamos un
botn es Click, as que Conclusin
usamos el operador +=, Poder acceder a la enorme librera de .NET
que podramos llamar con IronPython nos permite crear aplicacio-
suma y sigue, para aa- nes grficas multiplataforma. Una posibili-
dirlo a lista de funciones o dad realmente esperanzadora para todos
mtodos a llamar cuando aquellos que quieran llevar sus desarrollos
aparezca ese evento. Es en Python de un equipo a otro.
realmente sencillo. El lector puede profundizar en el
Pero, qu hacemos desarrollo de aplicaciones que empleen
Figura 3: Dilogo de eleccin de fichero. cuando pulsamos el botn WinForms desde IronPython en el Recurso
en el Listado 3? El objetivo [7]. Es un tutorial, en ingls, en el que se da
de lneas de cdigo. Vayamos a algo ms de este programa es crear un editor de textos un repaso a los conocimientos bsicos de
interesante. sencillo, ver Figura 2. Para ello, en la iniciali- desarrollo de aplicaciones grficas mediante
zacin de la clase LectorTXT hemos puesto WinForms.
Un Visor de Textos en la ventana un par de etiquetas, una para Python est consiguiendo con IronPython
Vamos a crear un visor de ficheros de texto saludar, otra para decir qu fichero estamos atraer a gran nmero de desarrolladores de
muy simple cuyo objetivo es explicar cmo abriendo, un botn y un rea de texto. otros sistemas operativos, de forma que se
funciona la gestin de eventos en Win- Cuando pulsamos en el botn aparecer estn comenzando a crear aplicaciones de
Forms. Este es quiz el punto dbil de el cuadro de dilogo de la Figura 3. Para cre- las que nosotros podremos disfrutar en
muchas libreras grficas, pero en el caso de arlo slo hemos tenido que crear una instan- Linux gracias a Mono.
WinForms se ha simplificado mucho. cia de OpenFileDialog dentro de abreFi- .NET se va estableciendo poco a poco en
Las aplicaciones grficas estn vivas. No chero(). Este objeto define un cuadro de di- el mundo empresarial como un estndar a
son programas insensibles que realizan ope- logo, pero no lo muestra, antes debemos tener en cuenta. Pero esto no debe atemori-
raciones, generan una salida y mueren. El configurarlo. Slo queremos mostrar fiche- zar a los que usen Python. Tanto si triunfa
trmino exacto es interactivas. Para poder ros de texto, por lo que creamos un filtro que .NET o Java, IronPython y Jython estn ah
interactuar con el usuario deben ser capaces nicamente permite ver ficheros de texto. para que Python siga vigente y demos-
de responder a acciones que el usuario rea- Para ello debemos almacenar en color.Filter trando al mundo que la programacin no
liza. una cadena con un formato especial: tiene por qu ser complicada.
A estas acciones se les denomina eventos.
Cuando creas una aplicacin grfica debes * Texto a mostrar | filtro Recursos
responder ante estos eventos con acciones, [1] Sitio de IronPython: http://www.
por evento existe una accin. Esta es la teo- El filtro debe ser como los que empleamos codeplex.com/IronPython
ra bsica. cuando usamos el terminal de Linux. El [2] Jim Hugunin anuncia IronPython:
En lo que difieren las diferentes libreras smbolo * significa cualquier cadena de http://blogs.msdn.com/hugunin/
grficas es en cmo se implementa la res- texto, por lo que nuestro filtro solo permitir archive/2006/09/05/741605.aspx
puesta a los eventos. Los desarrolladores de ver aquellos ficheros compuestos por cual- [3] Miguel de Icaza anuncia la imple-
WinForms estaban hartos de tener que quier cadena de texto y la terminacin .txt, mentacin libre de C#: http://tira-
escribir complicadas sentencias slo para que generalmente se emplea con los fiche- nia.org/blog/archive/2007/Jan-11-
hacer que cuando pulses un botn cambie ros te texto. El cuadro dilogo tambin tiene 1.html
de color. As que crearon el siguiente un ttulo, puesto que aparece en una ven- [4] API GDI+ para plataformas no Win-
dows: http://www.mono-pro-
diseo. tana independiente.
ject.com/Libgdiplus
Cada widget de la aplicacin posee una Cuando todo est configurado invocamos
[5] La licencia Apache de IronPython:
serie de eventos ante los cuales responde. color.ShowDialog(), que bloquea nuestra
http://www.codeplex.com/license?Pr
Estos eventos vienen representados como aplicacin hasta que el usuario ha escogido ojectName=IronPython
variables dentro del widget que guardan lis- un fichero. Esta llamada devuelve un resul-
[6] WinForms: http://www.mono-pro-
tas de acciones a realizar cuando tal evento tado, que debemos comprobar. En nuestro ject.com/WinForms
suceda. As de simple. Veamos el cdigo del caso, slo seguimos si el resultado ha sido [7] Tutoriales WinForms: http://www.
Listado 3. En el mtodo generaBoton1 apa- DialogResult.OK. Si el usuario cierra el cua- voidspace.org.uk/ironpython/
rece la sentencia de asignacin de cdigo a dro de dilogo, no se har nada por ejem- winforms/index.shtml
un evento: plo. [8] Listados de este artculo:
Si todo ha ido bien, entonces cogeremos http://www.linux-magazine.es/
self.boton1.Click +=U de color.FileName el nombre del fichero Magazine/Downloads/
self.abreFichero seleccionado y emplearemos un cdigo muy Especiales/06_Python

42 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

Desarrollo
Rpido!

Nedelcu Sorin, www.sxc.hu


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, Posee un entorno de desarrollo rpido cual debemos crear en primer lugar una
otros prefieren quedarse a tus espaldas, de interfaces profesional: designer ventana normal y corriente (llamada en
nerviosos, con los brazos cruzados y la Estas caractersticas hacen de Qt4 una ingls MainWindow), como la que
mirada fija en ti. El problema est claro: librera perfecta para el desarrollo rpido aparece en la Figura 2, y con la que tra-
viene en camino un gran envo, proba- de programas. Adems es una librera bajaremos durante todo el artculo. Por
blemente llegue hoy, con una gran canti- totalmente multiplataforma, podemos defecto viene con un barra de estado y
dad de productos que se deben controlar desarrollar una sola vez y ejecutar donde otra de men, con la que comenzare-
porque en unos das saldrn con destino queramos. mos. Para ello hay que pulsar dos veces
a un cliente. La ltima vez se hizo todo
el proceso con hojas de clculo, pero fue Designer
un desastre. Esta vez quieren estar pre- Una de las ventajas de
parados. Qt4 (ver Recurso [1])
Es necesario desarrollar una aplica- es su designer. Es una
cin y que funcione en menos de 2 herramienta, curtida en
horas. desarrollos profesiona-
Qt4 es la mejor opcin. les, que nos permite
comenzar diseando el
Qt4 aspecto grfico de la
Qt4 es la librera sobre la que se basa el aplicacin. Para ello
escritorio KDE4. Con licencia GPL, es slo tenemos que eje-
una alternativa perfecta para desarrollar cutar el comando
software libre. Posee algunas ventajas designer-qt4, y apare-
envidiables: cer un programa com-
Es muy completa (incorpora cdigo de puesto por distintas
acceso a base de datos, tratamiento ventanas como las que
svg, generacin de PDF) aparecen en la Figura
Es integrada (slo necesitas QT, no 1.
cientos de minipaquetes) Designer sirve para Figura 1: Designer permite crear interfaces de aplicaciones rpida-
PyQT es un proyecto maduro crear ventanas, para lo mente.

W W W. L I N U X - M A G A Z I N E . E S PYTHON 43
INTEGRACIN Qt

mos que Qt4 genere teclas rpidas para cido a lo que podemos ver en la Figura 4.
ellas. Por ejemplo, al poner un & Queda mal, no? Lo ideal sera que ocu-
delante de Archivo hacemos que su pase todo el espacio visible, para lo cual
primera A sirva como tecla rpida, tenemos que asignar un Layout (disposi-
activando el men cuando pulsemos cin) al espacio en el que pondremos
alt-a. El resultado sera el que vemos en nuestro Table View. Hay que pulsar con
la Figura 3. el botn derecho sobre el espacio gris
Vamos a trabajar con datos, as que lo que rodea al Table View, ver Figura 5, y
Figura 2: Nuestro MainWindow vaca y sin mejor que podemos hacer es emplear el pulsar en la ltima opcin del men: Lay
widgets. widget Table View, que nos permite tra- out. En ella escogeremos Lay out Verti-
bajar con datos tabulares y que podre- cally, y nuestro Table View ocupar todo
con el ratn sobre Type Here y escribir mos conectar, ms adelante, con nuestra el espacio.
&Archivo. De esta forma habremos base de datos de forma casi directa. Para Pulsamos dos veces con el ratn sobre
creado nuestro primer men. Dentro de ello tenemos que ir a la ventana principal el Table View y aparecer una ventana
este men introduciremos, de igual de Designer y buscar en Item Views el que nos permitir cambiar su nombre.
forma, la accin &Salir. El smbolo & widget Table View. Debemos arrastrarlo Esto es muy importante, ya que poste-
delante de las palabras indica que quere- hasta la ventana, quedando algo pare- riormente nos referiremos a Table View

Listado 1: Nuestro Programa


01 #!/usr/local/bin/python self.eliminarLinea ) 51 def eliminarLinea(self):
02 # -*- coding: utf-8 -*- 24 52 index = self.ui.tabla.
03 import sys 25 def generaModelo(self): currentIndex()
04 from PyQt4 import QtCore 26 self.conectaDB() 53 fila = index.row()
05 from PyQt4 import QtGui 27 modelo = QtSql. 54 ean13 =
06 from PyQt4 import QtSql QSqlTableModel(None, self.db) self.modelo.data(self.modelo.in
07 from gui import Ui_MainWindow 28 modelo.setTable(inventario) dex(fila, self.recordPro-
08 29 modelo.setSort( self. totipo.indexOf(ean13))).
09 class Programa(QtGui. recordPrototipo.indexOf(ean13 toString()
QMainWindow): ), QtCore.Qt.AscendingOrder) 55 nombre =
10 def __init__(self, 30 modelo.select() self.modelo.data(self.modelo.
parent=None): 31 return modelo index(fila, self.record
11 QtGui.QWidget.__init__(self, 32 Prototipo.indexOf(nombre))).
parent) 33 def conectaDB(self): toString()
12
34 self.db = QtSql.QSql 56
13 self.modelo = self.
Database.addDatabase(QPSQL) 57 if QtGui.QMessageBox.
generaModelo()
35 self.db.setHostName(rufus) question( self, Borrar linea,
14
36 self.db.setDatabaseName 58 QtCore.QString(
15 self.ui = Ui_MainWindow()
(inventario) Desea borrar el producto #%1,
16 self.ui.setupUi(self)
37 self.db.setUserName %2? ).arg(ean13).arg
17 self.ui.tabla.setModel
(josemaria) (nombre),
(self.modelo)
38 self.db.setPassword() 59
18 self.reajusta()
39 name = self.db.open() QtGui.QMessageBox.Yes|
19
40 query = QtSql.QSqlQuery( QtGui.QMessageBox.No) ==
20 QtCore.QObject.connect(
select * from QtGui.QMessageBox.Yes:
self.ui.action_Salir,QtCore.
inventario,self.db) 60 self.modelo.removeRow(fila)
SIGNAL(activated()),QtGui.qAp
41 self.recordPrototipo = 61 self.reajusta()
p, QtCore.SLOT(quit()) )
21 QtCore.QObject.connect( query.record() 62
self.ui.refrescar,QtCore. 42 63 def refrescar(self):
SIGNAL(clicked()),self.refres 43 def reajusta(self): 64 self.modelo.select()
car ) 44 self.ui.tabla.resize 65
22 QtCore.QObject.connect( ColumnsToContents() 66 if __name__ == __main__:
self.ui.nuevaLinea,QtCore. 45 67 app = QtGui.QApplication(
SIGNAL(clicked()),self.nueva- 46 def nuevaLinea(self): sys.argv)
Linea ) 47 fila = self.modelo.rowCount() 68 myapp = Programa()
23 QtCore.QObject.connect( 48 self.modelo.insertRow(fila) 69 myapp.show()
self.ui.eliminarLinea,QtCore. 49 self.reajusta() 70 sys.exit(app.exec_())
SIGNAL(clicked()), 50

44 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Qt INTEGRACIN

A los botones hay que dar-


les nombre. Para hacerlo
slo tenemos que pulsar el
botn derecho sobre ellos y
elegir Change objectName.
Los llamaremos, respectiva-
mente: refrescar, nuevaLi-
nea, eliminarLinea. El
ltimo retoque consiste en
asignarles unos iconos, de
Figura 3: Nuestro men, esperando las forma que sean identifica-
opciones. bles visualmente. Para esto
lo mejor es darse un paseo
por este nombre, por lo que lo bautizare- por los directorios de iconos
mos con uno que sea claro y conciso de Gnome o Kde y seleccio- Figura 4: Table View descolocada hacia el lado izquierdo.
(aunque no muy imaginativo): tabla. nar los ms acordes. Los
Nuestra tabla, tal como aparece aqu, copiamos a nuestro directorio de face), que llamaremos gui.ui. Este
nos permitira ver datos, incluso editar- desarrollo, y en designer pulsamos la fichero describe la interfaz grfica que
los, pero no aadir ni tampoco borrar. combinacin de teclas control-i. Apare- hemos diseado, aunque no lo podemos
Para ello necesitamos botones que ini- cer el Property Editor que nos permite emplear directamente, sino que hay que
cien acciones externas. Emplearemos cambiar los parmetros de los widgets. compilarlo. Para ello utilizamos el pro-
Tool Buttons que nos permiten utilizar Estamos interesados en los de los boto- grama pyuic4:
imgenes en su interior. Estos botones nes, por lo que slo tenemos que selec-
los solemos ver en las aplicaciones justa- cionar un botn para que el Property Edi- josemaria@rufus> pyuic4U
mente debajo del men. Dispondremos 3 tor cambie para mostrar los parmetros gui.ui > gui.py
de estos botones: uno para refrescar los de ese botn. Vamos a cambiar el par-
datos, uno para aadir y otro para borrar. metro Icon, que est en la seccin QAbs- El resultado de compilar este fichero es la
El botn de refresco es muy importante, tractButton. Si pulsamos en l veremos generacin de un fichero Python que rea-
puesto que al trabajar varias personas a que a la derecha hay un botn que nos liza todo lo necesario para que tengamos
la vez en la base de datos puede darse el permite seleccionar entre Choose nuestra interfaz grfica funcionando.
caso de que necesitemos saber si se ha Resource y Choose File.... La primera Ahora necesitamos crear un programa
introducido ya algn dato en particular. opcin sirve para guardar en un solo que, usando este fichero como librera,
Estos 3 botones los situaremos entre el fichero, llamado de recursos, todos los gestione la aplicacin que estamos cre-
men y la tabla, quedando como puede ficheros que vaya a necesitar nuestra ando.
apreciarse en la Figura 6. Pero, por qu aplicacin, lo que simplifica la instala- Miramos nuestro reloj, slo han
aparecen as? No sera mejor que apare- cin. La segunda opcin, ms simple, pasado 15 minutos, nuestros compaeros
ciesen a lo ancho? S, lo sera, pero hemos nos permite
indicado a designer que disponga los wid- indicar el nom-
gets verticalmente. La solucin consiste bre del fichero
en indicarle ahora que los disponga hori- que empleare-
zontalmente, para lo cual slo tenemos mos. Utilizare-
que seleccionarlos con el ratn (como si mos la segunda
fueran ficheros, enmarcndolos en un opcin, Choose
cuadrado de seleccin), pulsar el botn File..., por ser
derecho sobre ellos y en Lay outs selec- ms simple.
cionar Break LayOut. Con esta accin Seleccionamos
deshacemos la disposicin que elegimos un fichero gr-
anteriormente, pero para rehacerla, vol- fico con el
vemos a pulsar el botn derecho, y para icono, y hace-
estos tres botones seleccionados elegimos mos lo mismo
Lay out Horizontally, dejndolos como para los otros
aparecen en la Figura 7. Volvemos a pul- botones, ver
sar con el botn derecho, aunque ahora Figura 9.
sobre el rea gris entre los botones y el Cuando guar-
Table View, y seleccionamos Lay Out Ver- demos nuestro
tically, quedando todos los widgets como diseo generare-
en la Figura 8, con lo que prcticamente mos un fichero
hemos acabado con el diseo de la inter- con extensin Figura 5: El men Lay out nos permite colocar correctamente los ele-
faz. Nos quedan unos retoques. .ui (User Inter- mentos.

W W W. L I N U X - M A G A Z I N E . E S PYTHON 45
INTEGRACIN Qt

miran por encima de nuestro hombro Esta tcnica consiste en


tranquilos. Pero sabemos que esta inter- separar el cdigo de manipu-
faz necesita de un cerebro para contro- lacin de los datos, el cdigo
larla, por el momento es slo una que los muestra y el que
fachada. toma las decisiones. Qt pro-
vee dos clases diferentes, una
La Base de Datos que representa los datos (el
Usaremos una base de datos remota Post- modelo) y otra que repre-
gresql para almacenar los datos, de esta senta la visualizacin de los
forma varias personas podrn trabajar a mismos (el visualizador),
la vez en el programa. En lugar de dejando el control de la apli- Figura 8: sta ser la disposicin final de los botones y
emplear las libreras que existen para tra- cacin en nuestras manos. El tabla.
bajar con Postgresql en Python usaremos modelo es una entidad aut-
Qt4. La tabla que crearemos ser tambin noma e independiente, no requiere de la La primera de ellas realiza un select sobre
sencilla: existencia de una base de datos. Pode- la tabla inventario, lo que no es muy
mos crear un modelo con tablas y filas, extrao, pero la segunda lnea emplea el
create table inventario ( con relaciones y llaves, tal y como si resultado del select para generar un proto-
ean13 char(13) primary key, tuvisemos una base de datos. La razn tipo. Hacemos esto porque necesitamos
nombre varchar not null, detrs de este diseo es la de unificar la saber el formato de una fila de la tabla
cantidad int not nullU manipulacin de los datos en toda la inventario, para saber el nombre de las
default 0, aplicacin, estandarizndola. Las aplica- columnas y sus posiciones. Qt denomina
constraintU ciones empresariales siempre tienen que a esta informacin Record, y nos permitir
inventario_cantidad_positivoU interactuar con una base de datos, por lo realizar consultas posteriormente.
check(cantidad >= 0) que esta decisin de diseo es cada vez El mtodo generaModelo() es ms inte-
) ms comn. resante. Una vez realizada la conexin a
Por tanto, podemos crear nuestro la base de datos necesitamos generar un
Qt4 trae consigo una librera de control modelo de forma independiente, pero lo modelo de la tabla. Todos los modelos
de base de datos muy avanzada: QtSql. mejor no es eso. Lo realmente intere- descienden de una clase comn, de forma
No se restringe a mandar cdigo sql, ade- sante es que podemos conectar ese que son intercambiables. Existen dos
ms interacta directamente con los wid- modelo con una base de datos y seguire- modelos de base de datos: QSqlTableMo-
gets de Qt usando un patrn de diseo mos manipulando los datos empleando del, ms sencillo, y QSqlRelationalTable-
denominado MVC (modelo vista con- slo el modelo. Esto quiere decir que no Model, que almacena tambin informa-
trolador). tendremos apenas que escribir cdigo cin sobre las relaciones entre distintas
SQL, el modelo se encargar de ello tablas. Nos conformamos con QSqlTable-
por 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 que-
datos. Hoy pueden estar en Post- remos manipular con setTable(). Es muy
gresql, maana en MySQL y interesante que los productos que aparez-
pasado 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? Vea- ordenamos el modelo con el mtodo set-
mos el cdigo del Listado 1. El Sort(). Acepta dos parmetros, la posicin
mtodo conectaDB() muestra cmo de la columna que se emplear para orde-
Figura 6: Creamos tres nuevos botones realizar 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, com- de las filas de la tabla, que posee el
parado con otras libreras. Las lti- mtodo indexOf() que nos devolver la
mas dos lneas de cdigo son algo posicin de una columna dada. La orde-
extraas. nacin puede ser QtCore.Qt.AscendingOr-
der (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 invo-
self.db) cando select(), que realiza un select
self.recordPrototipo =U sobre la tabla, y almacenando los datos
Figura 7: Y los alineamos a nuestro gusto. query.record() en el modelo.

46 PYTHON 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 click-
ed() se pasa a un slot por
defecto del botn. Si quere-
mos 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
acciones. C++ este proceso requera
de la compilacin del cdigo
Volvemos a mirar nuestro reloj, han fuente con un compilador de Figura 10: Nuestra aplicacin en funcionamiento.
pasado 40 minutos. Nuestros compae- TrollTech, llamado moc, y poste-
ros se ponen nerviosos, no ven ningn riormente la compilacin con un compi- Nuestros compaeros se levantan para
avance. lador de C++. ver qu estamos haciendo y casi dan un
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 especifi- tiempo 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 espe-
antes. 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_Main- tenemos 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 dis-
mediante 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
datos, de forma que modelo y vista tam- modelo que vuelva a hacer un select() Conclusin
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 corres- La 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 colum- se 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 integra-
columnas 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 progra- obteniendo 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 programa- fila 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 aplica-
las deficiencias que posea. En particular, presentaremos un cuadro de dilogo pre- ciones o para desarrollo rpido, bajo pre-
se dieron de bruces con la dificultad de la guntando al usuario si desea borrar la sin. Al fin y al cabo ha hecho que nues-
programacin 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 proto-
decidi crear un compilador y una serie tipo para conseguir sus posiciones dentro Recursos
de instrucciones que hicieran la progra- de la fila y poder recuperar sus valores.
macin de interfaces grficas ms sim- Una vez confirmada la eliminacin, slo [1] Qu es PyQt?:
http://www.riverbankcomputing.co.
ple. Los detalles no son importantes, al tenemos que emplear el mtodo remove-
uk/software/pyqt/intro
fin y al cabo este artculo es sobre Row() del modelo.
[2] Listado de clases con sus explica-
Python, pero el nuevo modelo de trabajo Poco ms es necesario para que nues-
ciones 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
INFRAESTRUCTURAS Pyramid

$paster create --list-templates


El framework que no fue construido por aliengenas
Available templates:

Pyramid
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
Uno de los rivales de peso de Django est creciendo en popularidad poco a poco. pyramid SQLAlchemy project using U
url
Por Jos Mara Ruz
dispatch (no traversal)
pyramid_starter:pyramid starter U
Django es el framework que ms est donde vamos aadiendo rutas, vistas y project
contribuyendo a la extensin del uso de (como ya veremos) muchos otros tipos de pyramid_zodb: U
Python. Al igual que ocurri con Ruby y componentes. Configurator es la base sobre pyramid ZODB starter project
Ruby on Rails, Django se ha convertido la que montamos nuestro sitio web.
en una gran baza para la comunidad El concepto de vista es sencillo, al igual Con la opcin --list-templates podemos ver
Python y la excusa perfecta para probar que en Django, pudiendo usarse una funcin los proyectos que paster puede generar.
Python. Est bien documentado, dispone cualquiera que admita como parmetro un Pyramid nos ofrece varias alternativas,
de gran nmero de extensiones y el res- objeto Request con la informacin de la peti- desde la ms tradicional, empleando routes
paldo de grandes empresas por qu cin. Se distingue entre declarar una vista y y sqlalchemy, hasta otras ms exticas here-
querra alguien crear un competidor? emplearla en distintas rutas. Cada ruta tiene dadas de Zope, como pyramid_zodb o pyra-
La comunidad Python dista mucho de ser un nombre, route_name, que nos permite mid_alchemy. La diferencia entre ambas
monoltica, aparecen mltiples alternativas conectarla con cualquier ruta. As, los mto- posibilidades est en la forma en que se
para casi todo. Lo curioso es que el novato en dos add_route y add_view trabajan conjunta- estructuran las urls y en la base de datos a
los frameworks web es Django! Antes de su mente para definir el comportamiento de la usar.
aparicin ya existan otros distintos, y Pyra- web. Zope permite el uso de un sistema llamado
mid es el descendiente directo de algunos de Una vez hemos acabado con la traversal que genera automticamente las
ellos [1]. configuracin, podemos arrancar el servidor. urls empleando para ello las relaciones entre
Para este sencillo ejemplo hacemos uso de la los modelos de datos usados. La mayora de
Un hola mundo Minimalista funcin serve() de paste, que implementa un frameworks web, en casi todos los lenguajes
Podemos instalar Pyramid de muchas for- servidor web en Python que acepta como de programacin, se decantan en su lugar
om

mas, pero la ms cmoda desde el punto de parmetro una aplicacin WSGI, que obtene- por la definicin de las urls de forma expl-
.c
ile
ef

vista de Python consiste en crear un virtua- mos llamando a make_wsgi_app() de Confi- cita, para as tener ms control sobre ellas.
gu
or
m

lenv e instalar en su interior Pyramid: gurator. Nosotros nos conformaremos con el enfo-
Las rutas pueden contener parmetros en que tradicional, por lo que podemos crear el
$ virtualenv --no-site-packages U su interior que podemos capturar de distintas proyecto con:
--distribute pruebas-pyramid formas, como ya veremos.
$ cd pruebas-pyramid Para poder arrancar el servidor slo tene- $ paster create -t U
$ source bin/activate mos que ejecutar el fichero como un pro- pyramid_routesalchemy U
(pruebas-pyramid)$ pip U grama Python cualquiera e ir a la direccin ejemplo
install pyramid 127.0.0.1:8080.
Como resultado obtendremos un directorio
Con estos cuatro pasos dispondremos de un Creando un Proyecto llamado ejemplo que albergar nuestro pro-
virtualenv con las libreras que Pyramid La generacin de cdigo fuente, el llamado yecto, y en su interior un mdulo Python
necesita para funcionar. Es posible crear un scaffolding, es, a da de hoy, un elemento llamado tambin ejemplo. Para poder arran-
proyecto Pyramid con el comando paster (un indispensable de la mayora de los frame- car el proyecto tenemos que generar pri-
proyecto como los que creamos con Django), works web. Pyramid no provee directa- mero un fichero de configuracin e instalar
pero como vamos a generar una primera mente un sistema de scaffolding, lo que ira los paquetes necesarios mediante el
aplicacin minimalista, slo necesitamos en en contra de su poltica de reutilizacin. En comando:
principio un fichero con el contenido que lugar de ello hace uso de paster, un sistema
aparece en el Listado 1. independiente de scaffolding que compar- $ cd ejemplo
A diferencia de Django, Pyramid es un sis- ten otros proyectos. Cuando instalamos $ python setup.py develop
tema bastante estructurado y centrado en Pyramid con pip se instal paster como
componentes. La configuracin se efecta a dependencia, por lo que podemos usarlo Pyramid trae dos configuraciones: develop
travs de una instancia de Configurator, directamente: para desarrollo y production para el entorno

48 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Pyramid INFRAESTRUCTURAS

Renderers, config.include(pyramid_jinja2)
Vistas y config.add_renderer(.html,U
Plantillas pyramid_jinja2.rendererU
Pyramid permite _factory)
configurar dife- config.add_static_view(staticU
rentes renderers , ejemplo:static)
que pueden convi- config.scan()
vir en el mismo config.add_route(portada, /)
proyecto. Por ejem-
plo, podemos con- La llamada a add_renderer() es la que nos
figurar varios siste- permite indicar a Pyramid que las plantillas
mas de plantillas a con extensin .html debern ser renderizadas
la vez (Mako, Cha- empleando Jinja2. Adems llamamos a
maleon, Jinja2,) scan(), que se encargar de buscar las vistas
Figura 1: Pgina por defecto de Pyramid y debug_toolbar. y hacer que Pyra- que definamos y nos ahorrar el tener que
mid seleccione el aadirlas una a una con add_view(), como
de produccin. Cada una aparecer como un correcto basndose en la extensin de la vimos en el Listado 1. Pero para que esto sea
fichero con extensin .ini que nos permitirn plantilla a usar. posible, nuestra vista debe cambiar la forma
configurar el proyecto. Mientras que otros fra- En este ejemplo usaremos Jinja2, un sis- de trabajar; debemos poner el siguiente
meworks, como Django, prefieren que la tema de plantillas muy parecido al empleado cdigo en ejemplo/ejemplo/views.py:
configuracin se haga usando cdigo Python, por Django pero ms flexible y potente. Pri-
en Pyramid decidieron optar por seguir mero tenemos que instalar la extensin de from pyramid.view import U
usando ficheros de configuracin .ini. Pyramid para Jinja2: view_config
Una vez haya finalizado el proceso pode- @view_config (route_name = U
mos arrancar el servidor web con paster: $ pp install pyramid_jinja2 portada, renderer = U
ejemplo:templates/portada.html)
$ paster serve U Una vez instalada debemos indicar a Pyra- def portada(request):
development.ini mid que cargue la extensin y con qu fiche- return {saludo:Hola mundo!!}
ros queremos que use Jinja2 (en nuestro
Podemos ver la pgina generada en la ruta caso los que acaben en .html). Debemos Para indicar el route_name y la plantilla que
http://localhost:6543. Esta pgina incluye a la modificar el fichero ejemplo/ ejemplo/ usaremos en la vista portada usaremos el
derecha una pestaa que nos da acceso al __init__. py, que alberga la configuracin decorador @view_config(). En l podemos
development toolbar de Pyramid, el cual nos para nuestro proyecto, y poner dentro de definir todos los parmetros que necesita
proporcionar informacin muy valiosa main: Pyramid para usar la vista. Con el parmetro
durante el desarrollo de la aplicacin, as renderer indicamos que queremos la plantilla
como enlaces a la documentacin de Pyra- config = U portada.html, que debe estar dentro del direc-
mid. Configurator(settings=settings) torio templates del mdulo ejemplo. Cada

Listado 1: Ejemplo de Pyramid Bsico


01 from paste.httpserver import serve 10 config = Configurator()
02 from pyramid.configuration import Configurator 11 config.add_route(index, /)
03 from pyramid.response import Response 12 config.add_route(hola, /{nombre})
04 13 config.add_view(hola_mundo, route_name=hola)
05 def hola_mundo(request): 14 config.add_view(hola_mundo, route_name=index)
06 nombre = request.matchdict.get(nombre, mundo) 15
07 return Response(Hola {0}!.format(nombre)) 16 app = config.make_wsgi_app()
08 17 serve(app, host=0.0.0.0)
09 if __name__ == __main__:

Listado 2: Vista que Procesa Parmetros


01 from pyramid.view import view_config 10 saludo = Hola {0}.format(nombre)
02 11
03 @view_config(route_name=portada, 12 return {saludo: saludo}
04 renderer=ejemplo:templates/portada.html) 13
05 def portada(request): 14 @view_config(route_name=formulario,
06 saludo = Hola mundo!! 15 renderer=ejemplo:templates/formulario.html)
07 if request.POST: 16 def formulario(request):
08 nombre = request.params. get (nombre, saludo) 17 return {}
09 if nombre:

W W W. L I N U X - M A G A Z I N E . E S WWW.LINUX- MAGAZINE.ES PYTHON 49


INFRAESTRUCTURAS Pyramid

mdulo puede disponer de sus propias plan- La extensin pyramid_


tillas independientes, lo que aumenta la jinja2 se encarga de con-
modularidad del diseo. Adems, como la vertir la ruta ejemplo:tem-
plantilla acaba en .html, Pyramid emplear el plates /base. html en una
renderer Jinja2. ruta del sistema de fiche-
Jinja2 permite establecer herencia entre ros que Jinja2 pueda uti-
plantillas, por lo que crearemos una plantilla lizar. Vamos a aadir una
ejemplo/ejemplo/templates/base.html: nueva vista para demos-
trar cmo funcionan los
<html> enlaces y los formularios
<body> (ver Listado 2, Listado 3
<h1>Bienvenido<h1> y Listado 4).
<hr/> Creamos una nueva Figura 2: El comando top funcionando en nuestro navegador.
{% block contenido %} vista que apuntamos a
{% endblock %} la ruta por defecto de nuestro proyecto. De procese el formulario. De esta forma pode-
</body> esta forma la pgina principal mostrar un mos decidir cambiar qu vista lo procesar
</html> formulario para que podamos pasar un nom- siguiendo cualquier criterio que queramos,
bre. En el Listado 4 podemos ver el cdigo de puesto que la asignacin de un route_name a
Y otra plantilla ms llamada ejemplo/ejem- la plantilla ejemplo/ejemplo/templates/for- una vista puede variar durante la ejecucin
plo/templates/portada.html: mulario.html, donde generamos la url que de la llamada (por ejemplo, empleando crite-
procesar el formulario as: rios de seguridad, o si el usuario est regis-
{% extends ejemplo:U trado o no).
templates/ base.html %} <form action = U En el Listado 2 podemos observar que el
{% block U {{request.route_url U tratamiento de los datos es rudimentario.
contenido %} (portada)}} method=post> Pyramid no cuenta con una librera procesa-
<h2>{{saludo}}U dor de formularios como Django, sino que
</h2> A request.route_url() le pasamos el dependemos del uso de una librera externa.
{% endblock %} 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) 05 config.scan()
02 config.include(pyramid_jinja2) 06 config.add_route(portada, /hola)
03 config.add_renderer(.html, pyramid_jinja2.ren- 07 config.add_route(formulario, /)
derer_factory)
04 config.add_static_view(static, ejemplo:static)

Listado 4: Plantilla formulario.html


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

Listado 5: Plantilla top.html


01 {% extends tion() { 22 </script>
ejemplo:templates/base.html %} 11 socket.send({type: connect, 23 <style>
02 {% block extrahead %} userid: 123}); 24 #htop {
03 <script 12 }); 25 font-family: monospace;
src=http://code.jquery.com/jqu 13 socket.on(message, func-
ery-1.6.2.min.js></script> 26 font-size: 12pt;
tion(obj) {
04 <script 27 background: black;
14 if (obj.type == showdata) {
src=http://cdn.socket.io/sta- 28 color: green
ble/socket.io.js></script> 15 console.log(Message,
JSON.stringify(obj)); 29 }
05 <script> 30 </style>
16 txt = obj.txt;
06 var socket = null; 31 {% endblock %}
17 $(#htop).html(txt);
07 var txt = null 32 {% block contenido %}
18 }
08 $(function() { 33 <h2>htop</h2>
19 });
09 socket = new io.Socket(null, 34 <pre id=htop ></pre>
{}); 20 socket.connect();
21 }); 35 {% endblock %}
10 socket.on(connect, func-

50 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Pyramid INFRAESTRUCTURAS

ms conocidas son FormEncode y FormAl- En el Listado 5 podemos ver el cdigo de mostrar htop como en la url
chemy [5] [6] . En nuestro caso he decidido la plantilla htop.html. En ella cargamos tanto http://127.0.0.1:6543/htop como si se estu-
procesar a mano la peticin. jquery como la librera socket.io.js, que se viese ejecutando en l! Es fcil imaginar las
encarga de establecer un canal continuo de posibilidades de esta tecnologa (ver Listado
Un Ejemplo Ms Potente comunicacin entre el navegador y el servi- 7 para configuracin).
De verdad compensa la flexiblidad que nos dor. Si el navegador soporta websockets, los
aporta Pyramid? Con Django es todo mucho usar, pero en caso contrario tratar de inter- Conclusin
ms sencillo, puesto que las decisiones sobre actuar usando otros mecanismos. Lo mejor Si bien Django es el framework web domi-
qu libreras emplear ya han sido tomadas. de socket.io es que nos permite programar nante en Python, Pyramid puede ser una
Adems, todas las libreras estn controladas toda la interaccin mediante mensajes. alternativa muy interesante si necesitamos
por el proyecto, por lo que su integracin es Cuando recibimos un mensaje showdata, realizar una aplicacin web que no sea tradi-
perfecta. cambiamos el texto de la etiqueta con id htop cional. Su sistema basado en componentes
Personalmente creo que Pyramid por el que recibimos del servidor. Realmente nos da acceso a libreras de alta calidad y
comienza a rendir cuando necesitamos hacer sencillo verdad? No tenemos que ser cons- realmente potentes que normalmente son
cosas que no son tradicionales. Uno de los cientes ni siquiera sobre cmo se recibe el un fastidio integrar. En este artculo slo
problemas de Django consiste en que fue mensaje o cmo se procesa. hemos rascado la superficie de Pyramid
diseado en un entorno muy bien definido: El Listado 6 muestra el cdigo donde real- que cuenta con libreras realmente avanza-
un peridico. Django no se encuentra muy mente ocurre la magia. Creamos una sub- das para autenticacin, por ejemplo pero
bien preparado para el entorno actual, donde clase de SocketIOContext, donde conecta- espero que el lector haya podido dar sus pri-
tecnologas como HTML5 o websockets mos y mandamos un mensaje connected al meros pasos con el framework que no ha
comienzan a ser cada vez ms importantes. navegador remoto. Seguidamente definimos sido construido por aliengenas! [8].
Como ejemplo final de Pyramid vamos a la funcin sendhtop, que ser ejecutada por
crear una pgina que emplear socket.io [7] gevent como si fuese una hebra indepen- Recursos
para mandar datos a nuestro navegador en diente para cada conexin que recibamos.
[1] Pyramid: https://docs.pylonsproject.
tiempo real, lo que definitivamente no es la En dicha funcin ejecutamos htop con dos org/projects/pyramid/dev/
tpica pgina web tradicional. parmetros que le indican que slo nos [2] Pylons: https://www.pylonsproject.
Instalamos las libreras necesarias: muestre el estado de los procesos una vez y org/
pare su ejecucin. De esta manera podemos [3] Turbogear: http://turbogears.org/
pip install gevent U obtener una instantnea de la situacin de [4] Repoze.bfg: http://bfg.repoze.org/
gevent-websocket gevent-socketio nuestro ordenador. La salida de htop la man- [5] FormEncode: http://www.formencode.
damos en un mensaje showdata al navega- org/en/latest/index.html
stas nos permitirn arrancar nuestro pro- dor e indicamos a gevent que espere 1 [6] FormAlchemy: http://code.google.
yecto con un servidor basado en gevent en segundo. com/p/formalchemy/
lugar de usar paster, lo que nos permitir res- Todo esto se ejecutar en un bucle infinito [7] Socket.io: http://socket.io/
ponder a consultas continuadas sin necesi- mientras el navegador est conectado, cosa [8] Pyramid, not built by aliens!: https://
pylonsproject.org/denials/pyramid.
dad de cerrar el canal de comunicacin con que sabremos con el resultado de self.io.con- html
el navegador. nected(). De esta forma nuestro navegador

Listado 6: Conexin socket.io en views.py


01 from pyramid_socketio.io import 12 cmd = top -b -n 1 22 print Socket.IO request run-
SocketIOContext, socketio_manage 13 p = subprocess.Popen(cmd, ning
02 import gevent shell=True, 23 retval = socketio_manage(Connec-
03 stdout=subprocess.PIPE) tIOContext(request))
04 class ConnectIOContext(SocketIO- 14 txt = p.communicate()[0] 24 return Response(retval)
Context): 15 self.msg(showdata, txt = txt) 25
05 def msg_connect(self, msg): 16 gevent.sleep(1.0) 26 @view_config(route_name=top,
06 self.msg(connected) 17 27 renderer=ejemplo:templates/
07 import subprocess 18 self.spawn(sendtop) htop.html)
08 19 28 def htop(request):
09 def sendtop(): 20 @view_config (route_name= 29 return {}
10 prev = None socket.io)
11 while self.io.connected(): 21 def socket_io(request):

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


01 config = Configurator(settings=settings) 06 config.add_route(portada, /hola)
02 config.include(pyramid_jinja2) 07 config.add_route(formulario, /)
03 config.add_renderer(.html, pyramid_jinja2.ren- 08 config.add_route(socket.io, socket.io/*remain-
derer_factory) ing)
04 config.add_static_view(static, ejemplo:static) 09 config.add_route(top, /htop)
05 config.scan()

W W W. L I N U X - M A G A Z I N E . E S 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 (tenemos entendido que Google lo usa tam- Comencemos
del cdigo abierto un nuevo web framework bin internamente en algunas tareas). La publicacin de la versin 1.0 oficial de
[1]. Slo tres aos despus desde su publi- Django es, adems, la fundacin del gestor Django se produjo el da 2 de Septiembre de
cacin, Django tiene ya el suficiente atrac- de contenidos comercial Ellington, usado en 2008, tal y como estaba planeado en su hoja
tivo como para alentar la formacin de la varias organizaciones de gran tamao del de ruta.
Django Software Foundation [2]. Con la for- mundo de las noticias, como por ejemplo el Siempre podemos obtener, usando Sub-
macin de la DSF, Django pasa a formar Washington Post. version, el cdigo ms reciente:
parte de una impresionante lista de proyec- Jacob Kaplan-Moss, presidente de la
tos con fundacin propia, entre los que se Django Software Foundation y uno de los svn checkout U
encuentran Apache, Perl y Python. creadores de Django, dijo que la fundacin http://code.djangoproject.com/U
fue creada con el fin de que el proyecto svn/django/trunk/
Qu es Django? pudiese dar el siguiente paso en su ciclo de
Django es un framework para el desarrollo vida como proyecto de cdigo abierto. Independientemente de la versin que use-
web con Python. Se trata de un juego de Obviamente hemos tenido xito a la hora mos, la instalacin de Django es muy senci-
libreras que permiten al desarrollador tra- de atraer a una comunidad grande, vibrante, lla. Estando conectados a Internet, ejecuta-
bajar en las partes de una aplicacin que por lo que sentimos que era hora de que mos como root:
verdaderamente importan sin tener que pre- Django perteneciese a la comunidad. Con
ocuparse por la infraestructura subyacente. la fundacin se garantiza su perpetuidad, python setup.py install
Django usa el patrn MVC como otros incluso aunque algunas personas o algunas
muchos frameworks (Ruby on Rails y los compaas perdiesen inters, coment. para instalar Django en el directorio site-
distintos frameworks en Perl y PHP). Kaplan-Moss seala que el proyecto packages o donde sea que tengamos la ins-
Una de las funcionalidades punteras de acepta ahora donaciones para mejorar talacin de Python. En nuestro ejemplo usa-
Django es su increble interfaz de adminis- Django y, en un futuro prximo, la fundacin remos SQLite como base de datos. De todas
tracin, que se construye automticamente soportar Django mediante reuniones de formas, Django soporta perfectamente Post-
para nosotros. En este artculo recorremos desarrolladores, mtines y otras actividades gresSQL y MySQL.
los pasos necesarios para la creacin de una comunitarias. Muchas de estas reuniones de Para usar SQLite, instalamos el paquete
pequea aplicacin de tipo Twitter, con la desarrolladores han tenido lugar antes de la pysqlite2 [5] y seguimos las instrucciones de
que veremos en accin esta interfaz de publicacin de Django 1.0 -- y Kaplan-Moss la instalacin.
administracin. comenta que la fundacin ayudar a que las Django distingue entre proyectos y aplica-
Son muchos los sitios web de alto nivel personas ms indispensables puedan colabo- ciones. Por ejemplo, si hicisemos un sitio
que emplean Django en su desarrollo [3], rar al tiempo que asisten a las reuniones. Si web de gran tamao, con una seccin for-
como EveryBlock.com, Pownce.com o Tab- la fundacin ayuda a Django a avanzar, aun- mada por un blog, un foro o comercio
blo.com. Adems, es el framework predeter- que slo sea un poco ms rpido, con eso me online, entonces el sitio en s sera el pro-
minado incluido en el AppEngine de Google bastar, dijo Kaplan-Moss. yecto, mientras que el blog, el foro y el e-

52 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Django INFRAESTRUCTURAS

Figura 1: La interfaz de administracin de Django. Figura 2: Para aadir informacin a nuestra aplicacin personal.

comercio seran las aplicaciones. En realidad pleta a miprueba/prueba.db, el archivo de definimos el mtodo especial __unicode__,
slo es una forma de organizar los subpro- SQLite en el que vamos a guardar nuestra que le dice a Model cmo mostrar una ins-
yectos dentro del proyecto general. base de datos. La ruta completa depende del tancia del objeto en formato de cadenas (en
Para iniciar un nuevo proyecto ejecuta- directorio en el que hemos ejecutado el este caso, slo imprime la fecha y la entrada
mos startproject inicial. Aadimos dos elementos completa). Esta ser la informacin que
a la lista INSTALLED_APPS: django.con- usar el admin en el momento de mostrar
django-admin.py startproject U trib.admin para la interfaz de administra- los listados con las entradas de la base de
miprueba cin y la aplicacin miprpueba.Prueba, ase- datos. La clase vaca Admin indica a Django
gurndonos de que aadimos las comas. que queremos hacer uso de la interfaz de
con lo que se crea el directorio miejemplo Despus de definir qu base de datos administracin.
con unas pocas herramientas y archivos de vamos a usar, tenemos que construir nues- Para comprobar lo que llevamos hecho,
configuracin predeterminados. Luego tro modelo (Model), que es un objeto de validamos los modelos mediante:
necesitamos que Django genere los de la Python que define las tablas y columnas de
aplicacin, que llamaremos Prueba. Para SQL y su relacin. Puesto que la aplicacin python manage.py validate
ello, ejecutamos desde el directorio slo va a tener una tabla, slo definimos
miprueba: una clase. El archivo miprueba/Prueba/ Si todo est bien, debera devolvernos 0
models.py debera parecerse al que se mues- errors found. Django ya puede montar las
python manage.py startapp Prueba tra en el Listado 1. tablas de la base de datos. Para ello, introdu-
Primero importamos los asistentes de cimos
En miprueba/settings.py, colocamos DATA- modelo de Django y definimos la clase
BASE_ENGINE = sqlite3 y a Prueba, que contiene una columna de fecha python manage.py syncdb
DATABASE_NAME le ponemos la ruta com- y otra de texto para la entrada real. Luego
Vemos varias lneas con Creating table,
Listado 1: miprueba/Prueba/models.py algunas de las cuales pertenecen a permi-
sos de usuario/grupo, otras a admin y la
01 from django.db import models
ltima para la tabla Prueba. Es ahora
02
cuando Django nos insta a crear el super-
03 class Prueba(models.Model):
usuario para la interfaz de administracin.
04 fecha = models.DateField(Date`)
Debemos recordar el nombre de usuario y
05 entrada = models.CharField( max_length=`500` )
la contrasea, ya que las vamos a necesitar
06
luego.
07 def __unicode__(self):
Despus de crear las tablas para el
08 return %s %s` % (self.fecha, self.entrada)
modelo y la base de datos, habilitamos la
interfaz de administracin. Lo hacemos des-
Listado 2: Invertimos las Entradas comentando las tres lneas del archivo
miprueba/urls.py que se cre al ejecutar
01 from django.shortcuts import render_to_response
startproject. Las tres lneas estn etiqueta-
02 from models import Prueba
das, indicndonos que hay que descomen-
03
tarlas para que se habilite la administracin.
04 def todas_las_pruebas(request):
El archivo urls.py es con el que Django,
05 all_entries = Twit.objects.all().order_by(fecha).reverse()
mediante expresiones regulares, enlaza las
06 return render_to_response(todas_las_pruebas.html,{entradas:
diferentes URLs con las distintas partes de
todas_las_entradas })
nuestra aplicacin.

W W W. L I N U X - M A G A Z I N E . E S WWW.LINUX- MAGAZINE.ES PYTHON 53


INFRAESTRUCTURAS Django

diferentes, podemos especificarlo en el radas de todo lo dems, guardamos el


Listado 3: Hacemos la Plantilla comando: archivo en miprueba/plantillas/
01 <html> todas_las_pruebas.html.
02 <body> python manage.py U Como puede apreciarse, el lenguaje de
03 <table> runserver 127.0.0.1:5555 plantillas de Django es fcil de usar, aunque
04 <tr> tiene funcionalidades avanzadas. En sta
05 <th>Fecha</th> Suponiendo que lo hayamos ejecutado sin usamos un bucle for simple, con el que ite-
06 <th>Entrada</th> dicha opcin, nos dirigimos a http://127.0.0. ramos sobre los objetos Prueba que vamos a
07 </tr> 1:8000/admin, donde se nos instar a iden- pasar al mtodo TodasLasPruebas() y mos-
08 {% for t in entradas %} tificarnos para acceder a la interfaz de admi- trar los datos de cada objeto Prueba
09 <tr> nistracin. Despus de identificarnos, vere- mediante sus mtodos fecha y entrada.
10 <td>{{ t.fecha mos la pantalla que se muestra en la Figura Luego configuramos Django para que
}}</td> 1. encuentre nuestra plantilla en el sistema de
11 <td>{{ Debido a que estamos haciendo una apli- archivos, e instalamos una URL que enlace
t.entrada}}</td>
cacin personal, podemos ignorar por ahora con la vista. Para instalar los directorios de
12 </tr>
las secciones Sites y admin y pulsar sobre el plantillas en miprueba/settings.py hemos de
13 {% endfor %}
icono Add del cuadro Prueba. Veremos aadir la ruta completa a la lista TEM-
14 </table>
entonces algo como lo que se muestra en la PLATE_DIRS, que depender del directorio
15 </body>
Figura 2. desde el cual se ejecut startproject (debe-
16 </html>
Ya podemos introducir datos de entrada. mos asegurarnos de utilizar la ruta com-
Al pulsar sobre Today se rellena automtica- pleta). Despus editamos miprueba/urls.py
Adems, debemos crear un archivo mente la fecha actual, aunque podemos para mapear la URL (Listado 4).
admin.py. En este ejemplo slo usaremos la usar el calendario para escoger otra. Lo As hemos importado las vistas especfi-
configuracin predeterminada, pero ste es siguiente es introducir texto en Entrada y cas de la aplicacin, hemos aadido la URL
el lugar en el que se pueden personalizar pulsar sobre Save, que nos lleva a una /pruebas/ y hemos dejado como estaba el
varios de los aspectos de la interfaz de admi- pgina que nos muestra todas las Pruebas de mapeo de la interfaz de administracin. Si
nistracin. miprueba/Prueba/admin.py nuestra base de datos. Al pulsar sobre la nos dirigimos a http://127.0.0.1:8000/
necesitar contener: entrada que acabamos de hacer, llegamos a pruebas, veremos todas las Pruebas que
una interfaz de edicin, con la que podre- hemos aadido desde la interfaz de admi-
from django.contrib import admin mos realizar cambios sobre ella o eliminarla. nistracin.
from miprueba.Prueba.models U Es posible compartir las entradas de la El binomio formado por el servidor inde-
import Prueba web con otras personas. Para poder hacerlo pendiente y SQLite es genial para el
class PruebaAdminU debemos aadir una vista (o mdulo encar- desarrollo rpido, pero si lo que queremos
(admin.ModelAdmin): gado de la lgica) y una plantilla (la forma es montar una aplicacin en produccin es
pass en que se presentan los datos al usuario). mejor usar Apache con mod_python, junto
admin.site.registerU Aquellos que estn acostumbrados a otros con una base de datos ms robusta, como
(Prueba, PruebaAdmin) frameworks de tipo MVC, en los que la vista PostgreSQL (ver el sitio web de Django).
suele ser la plantilla en s misma, pueden Nuestro agradecimiento a Jacob Kaplan-
Para verlo en accin, ejecutamos tener un poco ms de dificultades a la hora Moss y a Adrian Holovaty por su contribu-
de acostumbrarse a las vistas de Django. cin a este artculo. Los listados de cdigo se
python manage.py runserver Para comenzar, editamos miprueba/ pueden descargar de [7].
Prueba/views.py, de forma que contenga un
que arranca un servidor de pruebas en la simple mtodo para devolver todas las Recursos
direccin http://127.0.0.1:8000. Para ini- entradas en orden cronolgico inverso (Lis-
[1] Sitio Web del proyecto Django:
ciarlo en una direccin IP o en un puerto tado 2). As definimos el mtodo TodasLas- http://www.djangoproject.com
Pruebas, que recoge todos los
[2] Django Software Foundation: http://
Listado 4: Mapear a URL objetos Prueba, ordenados por el
www.djangoproject.com/foundation
campo de fecha, y los invierte.
01 from django.conf.urls.defaults import * [3] Sitios desarrollados con Django:
Luego llama a render_to_res-
02 from django.contrib import admin http://www.djangosites.org
ponse(), con el nombre de la plan-
03 from mytwit import views [4] Descarga de Django: http://www.
tilla para la vista y un diccionario
04 djangoproject.com/download/
con los datos que le queremos
05 admin.autodiscover() [5] Pysqlite2: http://initd.org/pub/
pasar.
06 software/pysqlite/
Cuando hayamos acabado con
07 urlpatterns = Patterns(, [6] Django Book:
la vista, tendremos que hacer la
08 (r^twits/. http://www.djangobook.com
plantilla. El Listado 3 muestra un
mytwit.Twit.views.alltwits). [7] Cdigo del Artculo:
ejemplo de marcado simple con el
09 (radmin/(.*), admin.site.root), http://www.linux-magazine.es/
que captar la idea general. Con el
10 ) resources/article_code
fin de mantener las plantillas sepa-

54 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Thrift INFRAESTRUCTURAS

poner de soporte para l, y cuenta con al


Serializacin estilo Web 2.0
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 seriali-
zacin. En principio no existe nada que
nos impida usar bases de datos Sqlite
como formato para transportar nuestros

Benis Arapovic - 123rf.com


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 seriali-
zacin es vital para el funcionamiento de
nuestros sistemas, necesitamos algo ms
potente.
Hacer que distintos servicios se comuniquen entre ellos es todo un problema que Facebook
Instalacin de Apache Thrift
ha tratado de solucionar con Thrift. Por Jos Mara Ruz Thrift es un protocolo de serializacin
binario y tipado que define tanto datos
La informacin quiere ser libre es uno mos cmo Python puede usarla para como servicios (ver Figura 1). Al ser bina-
de los lemas hacker, pero habra que pre- comunicarse con un sistema Java que rio es mucho ms eficiente que los forma-
guntarse cmo se mover una vez que lo est ganando gran popularidad: Elastic- tos de texto, y puede enviar la misma can-
sea. En un mundo como el actual, donde Search. tidad de informacin de forma ms com-
la informacin y su procesado pueden pacta, ahorrando ancho de banda. Ade-
encontrarse distribuidos en decenas (o Serializando ms est tipado, lo que significa que toda
miles!) de servidores, es preciso contar Al acto de transformar un formato interno informacin que se transmita ser de un
con algn mecanismo que permita comu- de datos en algo que podamos transmitir tipo de dato determinado. Esto es impres-
nicar sistemas informticos de todo tipo o almacenar de forma externa se le suele cindible si queremos ser capaces de inter-
sin tener que recurrir a tecnologas de denominar serializar. Existe una cantidad cambiar datos con lenguajes tipados
bajo nivel. Este problema ha sido resuelto inimaginable de formatos que podemos como C#, Java o C++.
por muchos mediante el empleo del pro- usar para serializar datos, y Python viene Podemos descargar el cdigo fuente de
tocolo HTTP, pero cuando lo que bus- de serie con varias opciones: Thrift desde el enlace que aparece en el
camos es rendimiento, HTTP puede no XML Recurso 1. Para poder compilar Thrift
ser una opcin. JSON necesitaremos disponer de flex, as como
No es de extraar que dos de las empre- Pickle de la librera de desarrollo Boost en nues-
sas de referencia de Internet, Facebook y Sqlite tro sistema (en Ubuntu necesitaremos el
Google, se hayan enfrentado a este mismo Todos ellos cuentan con ventajas e incon- paquete libboost-dev, por ejemplo). Una
problema y hayan creado, de forma total- venientes. vez la tengamos instalada deberemos des-
mente independiente, dos tecnologas que XML es uno de los formatos ms exten- comprimir el fichero, compilarlo e insta-
buscan solucionarlo: Thrift [1] y Protocol didos del mundo. Al ser un formato de larlo. Es recomendable ejecutar el confi-
Buffers [2]. texto es fcilmente manipulable, y es gure indicando los lenguajes para los que
Tanto Facebook como Google necesita- posible editarlo a mano. Su nivel de com- no queremos generar un compilador,
ban una forma de poder comunicar siste- plejidad es seleccionable, podemos hacer puesto que tratar de generar los compila-
mas informticos desarrollados en dife- que sea tan complejo y almacene tanta dores de todos los lenguajes:
rentes lenguajes de programacin para informacin como deseemos.
que pudiesen intercambiar datos entre JSON fue la respuesta de la Web a la shell$ ./configure U
ellos. Por ms que queramos a nuestro complejidad de XML. Al igual que l, es -without-javaU
querido Python, cuando el rendimiento es un formato de texto, pero su estructura --without--csharp
una prioridad, no es nuestra mejor est cerrada. Es sorprendentemente sim- ...
opcin. Y puede ocurrir tambin lo con- ple y sencillo de manejar, lo que no shell$ make
trario: por mucho que nos guste Java, en ayuda demasiado en situaciones comple- shell$ sudo make install
numerosas ocasiones la velocidad de jas.
desarrollo con Python no tiene rival! Pickle es en realidad un mecanismo Podemos comprobar que se ha instalado
En este artculo echaremos un vistazo a propio de Python para la superlacin de correctamente ejecutando el compilador
la tecnologa Thrift de Facebook y vere- objetos. Ningn otro lenguaje parece dis- de Thrift:

W W W. L I N U X - M A G A Z I N E . E S PYTHON 55
INFRAESTRUCTURAS Thrift

Ya tenemos todo lo necesario para usar


Thrift, pasemos a usarlo.
Listado 1: Fichero hola.thrift
01 service Saludos {
Hola Mundo 02 string hola(1: string
nombre)
Thrift funciona como un compilador
03 }
(Figura 2) que acepta una descripcin de
los datos y servicios, la interfaz, que
vamos a utilizar, y genera a partir de ella tado 2 genera un servidor de red que res-
una librera en el lenguaje de destino que ponder a la interfaz declarada en el
codificar y decodificar ese formato en fichero hola.thrift, mientras que en Lis-
el lenguaje de programacin que le indi- tado 3 se encuentra el cdigo que usar
quemos. Es ms sencillo verlo con un este servicio. Para verlos en funciona-
ejemplo. Digamos que queremos dispo- miento tendremos que arrancar primero
ner de un servicio llamado hola que el servidor en un terminal:
acepta una cadena de texto y devuelve
otra cadena de texto. Lo primero que shell$ python servidor.py
necesitaremos es crear un fichero de des- Arrancando el servidor...
cripcin en el formato de Thrift como el
Figura 1: Esquema de trabajo con Thrift. que aparece en el Listado 1. Una vez ten- Y el cliente en otro terminal:
gamos el fichero listo, podemos generar
shell$ thrift el cdigo Python con el siguiente shell$ python cliente.py
Usage: thrift [options] file comando: Hola mundo
Options: shell$
-version Print the ?. shell$ thrift -gen pyU
.... hola.thrift Ha funcionado! Thrift nos permite arran-
car un servidor de red con el servicio que
Necesitamos un componente ms para El comando generar un directorio lla- hemos definido. La librera se encarga de
poder hacer uso de Thrift: la librera mado gen-py que contendr el mdulo prcticamente todo, lo que nos permite
python thrift. Instalarla es mucho ms que vamos a usar. Debemos copiar el concentrarnos en crear el cdigo fuente
sencillo: mdulo hola al directorio en el que vaya- de nuestro servicio. Pero es Thrift
mos a ejecutar tanto el script del Listado 2 rpido? Hagamos una prueba. Con el ser-
shell$ pip install thrift como el del Listado 3. El cdigo del Lis- vidor an arrancado, vamos a mandar

Listado 2: Fichero servidor.py


01 #!/usr/bin/env python 13 def hola(self,nombre): 22 pfactory = TBinaryProtocol. TBi-
02 14 return Hola {0}.format (nom- naryProtocolFactory()
03 import sys bre) 23
04 15 24 ## Arrancamos
05 from hola import Saludos 16 ## Pasos necesarios para arrancar 25 servidor = TServer.TSimple-
06 from hola.ttypes import * 17 ## el servidor Server(processor, transport,
07 18 handler = SaludosHandler() tfactory, pfactory)
08 from thrift.transport import 19 processor = 26
TSocket Saludos.Processor(handler) 27 print Arrancando el servidor...
09 from thrift.server import TServer 20 transport = TSocket.TServer- 28 servidor.serve()
10 Socket(9090) 29 print acabamos.
11 ## Servicio 21 tfactory = TTransport.TBuffered-
12 class SaludosHandler: TransportFactory()

Listado 3: Fichero cliente.py


01 import sys 10 18 ## Conectamos
02 from hola import Saludos 11 try: 19 transport.open()
03 from hola.ttypes import * 12 transport = TSocket.TSocket 20
04 from hola.constants import * (localhost, 9090) 21 cadena = cliente.hola(mundo)
05 13 transport = TTransport. 22 print cadena
06 from thrift import Thrift TBufferedTransport(transport) 23
07 from thrift.transport import 14 protocol = TBinaryProtocol.TBi- 24 ## Cerramos la conexin
TSocket naryProtocol(transport) 25 transport.close()
08 from thrift.transport import 15 26 except Thrift.TException, tx:
TTransport 16 cliente = Saludos.Client(proto- 27 print %s % (tx.message)
09 from thrift.protocol import TBi- col)
naryProtocol 17

56 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Thrift INFRAESTRUCTURAS

10000 mensajes mediante el cdigo del municaciones, ofreciendo varias posibili-


Listado 4: dades dependiendo del lenguaje de pro-
gramacin que usemos. En Python es
shell$ time python U posible usar un socket, el protocolo http o
test_velocidad.py Twisted. Por simplicidad vamos a usar un
socket, que es el protocolo de ms bajo
real 0m1.715s nivel, mediante la clase TSocket. Sobre el
user 0m0.820s protocolo de comunicaciones debemos
sys 0m0.156s montar un servidor, TServer, que atienda
los mensajes que lleguen y se los pase al
No est nada mal, siendo Python, y sin procesador. Como puedes ver, los nom-
usar ninguna optimizacin. Podemos bres son bastante descriptivos.
parar el servidor pulsando la combina- Thrift nos obliga an a hacer algunas
cin de teclas Control+C. elecciones. Debemos indicar al servidor
qu clase de protocolo vamos a usar en
Cmo Funciona el Cdigo Fuente nuestro caso TBinaryProtocol y cmo
Analicemos el cdigo del Listado 2. Carga- queremos que se comporte el servidor,
mos el mdulo hola resultado de nuestra usando un bfer con TBufferedTransport. Figura 2: Http vs Thrift.
definicin en el fichero hola.thrift, y que Thrift es configurable y nos ofrece dife-
se encontraba dentro del directorio gen-py. rentes opciones para casi todo. Podramos shell$ wget -c U
Este mdulo es en realidad el nombre del haber seleccionado un protocolo basado https://github.com/downloads/U
propio fichero, que Thrift ha convertido en en JSON mediante TJSONProtocol, por elasticsearch/elasticsearch/U
mdulo, por lo que hay que tener cuidado ejemplo. elasticsearch-0.16.2.tar.gz
con el nombre que demos al fichero .thrift. Ya slo nos falta arrancar el servidor, shell$ tar zxpf U
Dentro del fichero hemos definido un ser- instanciando por ejemplo TSimpleServer y elasticsearch-0.16.2.tar.gz
vicio llamado Saludos. Thrift nos permite llamando al mtodo server() que se blo- shell$ cd elasticsearch-0.16.2
reunir grupos de funciones y variables quear mientras no lleguen mensajes. shell$ cd bin
bajo servicios. As es muy sencillo organi- shell$ ./plugin -install U
zar nuestro cdigo. Pasamos a cargar el ElasticSearch transport-thrift
fichero ttypes que contiene todas las fun- Como ejemplo del uso de Thrift vamos a shell$ ./elasticsearch -f
ciones, objetos y variables que necesitare- crear un pequeo programa Python que
mos para usar Thrift. emplee esta tecnologa para interactuar Listo! Ya tenemos funcionando un motor
Nuestro servicio ser un objeto Python con el motor de bsqueda de moda: Elas- de bsqueda con ndice distribuido y que
con mtodos que tengan los mismos ticSearch [3]. ElasticSearch est revolu- se comunica usando Thrift. Es normal
nombres y parmetros que definimos en cionando el mundo de los motores de que ElasticSearch se est ganando el cora-
el fichero hola.thrift. Por convencin se bsqueda. Ofreciendo el rendimiento de zn de muchos desarrolladores. Debemos
aade la palabra Handler al nombre del Solr/Lucene, pero aadiendo la capacidad generar el mdulo de la interfaz de Thrift
servicio que vamos a implementar, en de trabajar de forma distribuida, reparte para Python. Para ello debemos descargar
nuestro caso SaludosHandler. el ndice de bsqueda entre varias mqui- el fichero elasticsearch.thrift de la direc-
Mediante el mtodo Saludos.Procesor(), nas de forma automtica. Est progra- cin que aparece en el Recurso 4. Y com-
indicamos qu objeto implementar la mado en Java y ofrece varios protocolos pilarlo:
interfaz definida. Al objeto resultante se de trabajo, siendo posible comunicarse
le suele llamar processor, puesto que su con el servidor ElasticSearch mediante shell$ thirft --gen U
funcin ser procesar peticiones. Rest sobre http o Thrift (Figura 3). py elasticsearch.thrift
En este punto podemos elegir cmo Para instalar ElasticSearch slo tene-
vamos a usar nuestro procesador. Thrift mos que descargarlo desde la direccin Cuando tengamos el directorio gen-py,
debe trabajar sobre algn protocolo deco- del Recurso 3 : extraemos de su interior el mdulo elas-

Listado 4: Test de Velocidad


01 import sys TTransport naryProtocol(transport)
02 sys.path.append(./gen-py) 11 from thrift.protocol import TBi- 16
03 naryProtocol 17 cliente = Saludos.Client(proto-
04 from hola import Saludos 12 col)
05 from hola.ttypes import * 13 transport = 18
06 from hola.constants import * TSocket.TSocket(localhost, 19 transport.open()
07 9090) 20
08 from thrift import Thrift 14 transport = 21 for i in range(0,10000):
09 from thrift.transport import TTransport.TBufferedTransport(t 22 cadena = cliente.hola(mundo)
TSocket ransport) 23
10 from thrift.transport import 15 protocol = TBinaryProtocol.TBi- 24 transport.close()

W W W. L I N U X - M A G A Z I N E . E S PYTHON 57
INFRAESTRUCTURAS Thrift

ticsearch y lo ubicamos en el mismo direc- Mtodo (POST, GET, PUT)


torio en el que pongamos el script del Lis- URI(<indice>/<modelo>/<id>.)
tado 5. Cuando lo ejecutemos, ste ser el Cabeceras (Headers)
resultado: Cuerpo del mensaje (Body)
Algunos mtodos exigen el uso del body
shell$ time python busqueda.py (por ejemplo los que requieren el mtodo
[{u_score: 0.38431653, U POST), mientras que otros slo requieren
u_type: uarticulo, U el URI (GET) Y por qu usamos nmeros
u_id: u1, para el tipo de mtodo usado? Si echamos
u_source: {utitulo: U un vistazo al fichero elasticsearch.thrift
uThrift, Python y U veremos que ah se declaran los nmeros
ElasticSearch}, que usaremos para los mtodos.
u_index: ulinuxmagazine}] En nuestro ejemplo podemos ver dife-
rentes mtodos en uso. Crear un ndice
real 0m0.353s exige un POST, aadir un modelo, un
user 0m0.040s PUT y hacer una consulta, un GET. Cada
sys 0m0.016s llamada de ejecucin de un request
devuelve un objeto RestResponse con un
Hemos creado un ndice, aadido un campo body, en el que encontraremos el
modelo de documento, insertado un resultado codificado en JSON.
documento y realizado 100 bsquedas en
300 milisegundos. ElasticSearch trabaja Conclusin
usando una API Rest que acepta coman- Thrift puede parecer algo complejo ahora Figura 3: Funcionamiento de ElasticSearch.
dos codificados en URIs (rutas) mediante que todos nos hemos acostumbrado a
los mtodos tpicos de HTTP. Tanto los emplear HTTP como protocolo para las Recursos
datos enviados como los recibidos se peticiones remotas. Pero existen muchas
[1] Tecnologas Thrift de Facebook:
codifican en JSON, que podemos codifi- situaciones en las que necesitaremos uti-
http://thrift.apache.org/
car y decodificar empleando la librera lizar un protocolo que consuma menos
json de Python. ancho de banda y ofrezca ms rendi- [2] Tecnologa Protocol Buffers de Goo-
gle:
El esquema de trabajo es parecido al miento. Tanto Facebook como Google
http://code.google.com/p/protobuf/
que hemos visto con anterioridad. Crea- han tenido que desarrollar su propia tec-
mos una conexin usando TSocket, espe- nologa para solventar este problema, y [3] Motor de bsqueda ElasticSearch:
http://www.elasticsearch.org/
cificamos el tipo de transporte y el proto- ambos han tenido la gentileza de libe-
colo (en este caso una variante del bina- rarla como software libre. Y por si fuese [4] Fichero thrift para ElasticSearch:
rio) y generamos un cliente. poco, ambos sistemas generan cdigo https://github.com/elasticsearch/ elas-
ticsearch/blob/master/plugins/ trans-
La clase RestRequest ha sido generada Python, todo un regalo para nuestra
port/thrift/elasticsearch.thrift
por Thrift y tiene cuatro parmetros: comunidad.

Listado 5: Interactuando con ElasticSearch


01 from thrift import Thrift 16 transport.open() ulo/1,
02 from thrift.transport import 17 34 headers={},
TTransport 18 ## Creamos un ndice 35 body= articulo)
03 from thrift.transport import 19 request = RestRequest(method=1, 36 respuesta =
TSocket uri=/linuxmagazine, client.execute(request)
04 from thrift.protocol.TBinaryPro- 20 headers={}, body=) 37
tocol import TBinaryProtocolAc- 21 client.execute(request) 38 ## Buscamos la cadena thrift
celerated 22 39 ruta = /linuxmagazine/artic-
05 23 ## Cargamos un modelo de documento ulo/_search?q=thrift
06 from elasticsearch import Rest 24 mapping = json.dumps({proper- 40 for i in range(0, 100):
07 from elasticsearch.ttypes import ties: { 41 request = RestRequest(method=0,
* 25 titulo : {type : string, 42 uri=ruta,
08 store : yes}}}) 43 headers={},
09 import json 26 request = RestRequest(method=2, 44 body= )
10 uri=/linuxmagazine/articulo, 45 respuesta =
11 socket = TSocket.TSocket(local- 27 headers={}, body= mapping) client.execute(request)
host, 9500) 28 client.execute(request) 46
12 transport = 29 47 print
TTransport.TBufferedTransport(s 30 ## Cargamos un documento json.loads(respuesta.body)[hit
ocket) 31 articulo = json.dumps({titulo : s][hits]
13 protocol = TBinaryProtocolAccel- Thrift, Python y Elastic- 48
erated(transport) Search}) 49 transport.close()
14 client = Rest.Client(protocol) 32 request = RestRequest(method=2,
15 33 uri=/linuxmagazine/artic-

58 PYTHON 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 tan de inters. Mucha gente incluso publi- consola de texto. Dichos interfaces an se
2123. ca sus ficheros de configuracin, de utilizan en numerosas aplicaciones, la
En todos los libros sobre administra- manera que siempre pueda acceder a razn es que son mucho ms simples de
cin de sistemas se nos recomienda llevar ellos. usar. Es ms fcil automatizar el pulsar
un pequeo cuaderno de bitcora donde Y que ocurre si solo lo queremos para tres veces TAB que mover el ratn y fun-
ir reflejando las acciones peligrosas que nosotros? Y si la mquina a la que esta- cionan mejor remotamente, an con
realicemos. De esta manera, se supone, mos accediendo no tiene un servidor web conexiones lentas.
podremos recrear paso a paso los eventos con el software adecuado configurado Vamos a disear y programar un cua-
que nos llevaron a un desastre y por tanto para tener un weblog? y si no queremos derno de bitcora en Python, que utilizar
ir deshacindolos en orden inverso. montar tanta parafernalia? ncurses para el interfaz texto y dbm para
La cruda realidad es que no todo el Algunas aplicaciones, como KPIM, almacenar las entradas por fecha.
mundo usa dichos cuadernos. Es pesado incorporan ya la opcin de llevar un dia-
tener que dejar el teclado y coger el bol- rio personal, pero no funcionan de forma Diseo del Cuaderno
grafo para escribir a mano! no estba- remota a no ser que tengamos una cone- Comencemos nuestro diseo echando un
mos en la era de los ordenadores? No xin de red con mucho ancho de banda. vistazo a las libreras en que nos vamos a
bamos a desterrar el papel? Qu opciones nos quedan? Podemos basar. ncurses fue desarrollada para abs-
Muchas personas usan un weblog en su volver nuestra mirada a la era antigua de traer, ocultar y simplificar la gestin de
propia mquina o en Internet para ir los ordenadores, cuando los interfaces terminales texto. Cada fabricante dotaba
apuntando detalles o noticias que le resul- funcionaban exclusivamente desde una a su terminal de texto de caractersticas

W W W. L I N U X - M A G A Z I N E . E S PYTHON 59
LIBRERAS Curses

Bsicamente tenemos que mostrar un


interfaz que divida la pantalla en dos par-
tes. En una deber mostrar las fechas
almacenadas, y debe permitir recorrerlas.
En la otra debe mostrar el texto relaciona-
do con la fecha indicada.
Figura 1: Hola Mundo en nuestro primer pro- Las acciones sern:
grama curses. Navegar entradas.
Crear entrada. Figura 2: Un cuadro de texto curses.
distintas a las del resto, forzadas la mayo- Editar entrada.
ra de las veces por una feroz competen- Borrar entrada. permite almacenar claves y valores aso-
cia. Esto converta en una tortura el sim- Salir. ciados a las mismas, as como recuperar
ple hecho de cambiar un terminal por Cada una de las acciones se corresponde- el valor o borrar las claves. Ni ms, ni
otro, requiriendo la mayora de las veces r con una combinacin de teclas. menos.
la modificacin del programa de turno. Comenzaremos creando los objetos que La librera dbm necesita un fichero
ncurses permita realizar programas sin gestionen los datos y posteriormente el donde depositar los datos que se alma-
tener en cuenta las diferencias entre los interfaz con el usuario. cenan. As, tendremos que darle el
terminales. No solo eso, sino que adems nombre de un fichero e indicarle como
simplific enormemente la gestin de Almacenamiento de datos queremos que lo trate. Puede abrir el
interfaces de texto como veremos ms Debemos conservar el texto asociado a fichero para introducir nuevos datos o
adelante. una fecha y hora en algn sitio. Con la crearlo de nuevo, aunque ya exista uno
dbm es una base de datos. Lo pongo fiebre actual por las bases de datos rela- con el mismo nombre.
entre comillas porque en realidad slo cionales pocas veces se menciona la Una vez abierto el fichero, un objeto
nos permite almacenar datos, recuperar- existencia otras bases de datos que no dbm se comporta como un contenedor
los y realizar bsquedas, pero no usando cumplen el estndar relacional ni SQL. cualquiera. Podremos hacer uso de la
SQL sino llamadas a libreras. <dbm> es Realmente se necesita un motor rela- sintaxis [] a la que nos tienen acos-
una familia de libreras que nos permiten cional y SQL para cualquier cosa que tumbrados la mayor parte de los lengua-
almacenar datos en un fichero y gestio- necesitemos almacenar? Por supuesto jes de programacin.
narlos como si fuesen un diccionario o que no. Desgraciadamente, cuando slo Como podemos observar en el Listado
hash en Python. Cada entrada se compo- tienes un martillo, todo te parece cla- 1, el uso de la librera dbm es realmente
ne de una clave y un valor asociado. Si no vos. simple. Se comporta como una lista,
tenemos que realizar bsquedas comple- El problema est en la definicin de con todas sus operaciones. El lector se
jas, dbm se convertir en nuestro mejor base de datos, dbm lo es pero sin habr preguntado al ver el cdigo:
opcin. mucha sofisticacin. Bsicamente nos Dnde est el truco? si dbm represen-

Listado 1: Ejemplo de uso de DBM


01 >>> import dbm 06 >>> datos.close() 12 [Juan Jose]
02 >>> datos = dbm.open(visi- 07 >>> 13 >>> for llave in datos.keys():
tantes,c) # crea el fichero 08 >>> datos = dbm.open(visi- 14 ... print [+llave+] -> +
03 >>> datos[Juan Jose] = vendra tantes) datos[llave]
el martes 09 >>> datos[Juan Jose] 15 ...
04 >>> datos[Juan Jose] 10 vendra el martes 16 [Juan Jos] -> vendra el martes
05 vendra el martes 11 >>> datos.keys() 17 >>> datos.close()

Listado 2: almacen.py
01 #!/usr/local/bin/python 15 contenido = 29
02 self.contenido(clave) 30 def __len__(self):
03 #!/usr/local/bin/python 16 if palabra in contenido: 31 return len(self.entradas())
04 17 encontradas.push(clave) 32
05 import dbm 18 33 def __setitem__ (self, clave,
06 class Almacen: 19 return encontradas valor):
07 def __init__ (self,nombre): 20 34 self.bd[clave] = valor
08 self.bd = dbm.open(nombre,c) 21 def entradas (self): 35
09 22 a = self.bd.keys() 36 def __getitem__(self,clave):
10 def busca_palabra (self, pal- 23 if not a: 37 return self.bd[clave]
abra): 24 a = [] 38
11 claves = self.entradas() 25 return a 39 def __delitem__(self,clave):
12 encontradas = [] 26 40 del self.bd[clave]
13 27 def cierra (self):
14 for clave in claves: 28 self.bd.close()

60 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Curses LIBRERAS

trada en la Tabla 1. Por tanto podemos


enmascarar las acciones de un objeto de
manera que se use como si fuese un dic-
cionario. Y precisamente eso es lo que
hacemos con nuestro objeto Almacen que
encubre un diccionario, aadiendo nue-
vas acciones. El lector puede comprobar
el cdigo en el Listado 2 (disponible en
Figura 3: Insercin de una nueva entrada en [1]). Figura 4: Vista de la bitcora con varias
la bitcora. entradas.
Curses
ta una base de datos por qu puede Curses son unas libreras de bajo nivel. un programa Python ejecutable (recuerda
hacer uso de la sintaxis []?. Las abstracciones que crean son muy hacer el chmod +x <programa>).
La respuesta es que en Python la sinta- bsicas: preparar consola, crear venta- Podemos ver un programa que inicializa
xis [] es lo que en ingls se llama nas (nada que ver con las grficas), la consola con curses en el Listado 3.
syntactic sugar. Por traducirlo de alguna escribir en esas ventanas, recoger caracte- Posteriormente escribimos un Hola
manera, viene a decir que es una manera res y poco ms. mundo y refrescamos la pantalla, pode-
de hacer agradable visualmente (y a Debido a ello son bastante complicadas mos ver el resultado en la Figura 1. Esta
nuestro pobres dedos) la llamada a ciertas de manejar. Hacer cosas vistosas suele lle- parte es vital, si no refrescamos la panta-
funciones del lenguaje. var mucho cdigo. Por ello nos vamos a lla curses no mostrar nada. En el Listado
Podemos incorporar [] a uno de centrar en un interfaz sencillo. Nuestro 3 stdscr representa toda la pantalla. Es
nuestro objetos y hacer que se comporte programa ser modal, tendr un modo de posible crear subventanas y hacer actuali-
como una lista? La respuesta es: S! y no navegacin y uno de edicin, al igual zaciones selectivas como podremos com-
tiene nada de complicado. que el editor Vi. Precisamente Vi fue probar en el cdigo del programa.
Python reserva unas serie de mtodos uno de sus primeros usuarios. Una vez realizadas las operaciones,
debido a su uso especial, entre ellos pasamos a dejar la pantalla en una
estn: Diseo Principal configuracin correcta, accin que reali-
def __len__(self) Comenzaremos por inicializar curses. Por zan las cuatro ltimas llamadas a funcio-
def __setitem__(self, clave, valor) desgracia, esto tambin nos hace perder nes.
def __getitem__(self, clave) el control de nuestra consola Python, El objeto diario crear a su vez un obje-
def __delitem__(self, clave) puesto que anula su funcionamiento. Por to GUI, que gestiona el interfaz, y el obje-
Estos cuatro mtodos los enmascara ello se pide al lector que ejecute todas las to Almacen que se encarga de gestionar la
python posteriormente de la manera mos- acciones relacionadas con curses desde base de datos.

Listado 3: Hola mundo con curses


01#!/usr/local/bin/python 08 curses.noecho() 15
02 # -*- coding: ISO8859-1 -*- 09 curses.cbreak() 16 # Limpiamos la pantalla
03 10 stdscr.keypad(1) 17 stdscr.keypad(0)
04 import curses 11 18 curses.echo()
05 12 # Escribimos algo 19 curses.nocbreak()
06 # Inicializamos la pantalla 13 stdscr.addstr(Hola mundo,0) 20 curses.endwin()
07 stdscr=curses.initscr() 14 stdscr.refresh()

Listado 4: Ejemplo de uso de Textbox


01#!/usr/local/bin/python 08 uly, ulx = 15, 20 12 stdscr.refresh()
02 # -*- coding: ISO8859-1 -*- 09 stdscr.addstr(uly-2, ulx, Use 13 return Textbox(win).edit()
03 Ctrl-G to end editing.) 14
04 import curses 10 win = curses.newwin(nlines, 15 str =
ncols, uly, ulx) curses.wrapper(test_editbox)
05 import curses.textpad
11 rectangle(stdscr, uly-1, ulx-1, 16 print Contents of text box:,
06 uly + nlines, ulx + ncols) repr(str)
07 ncols, nlines = 9, 4

Listado 5: Mtodo ejecuta_comando(self,ch)


01def ejecuta_commando(self, ch): 07 break 12 elif ch in (curses.ascii.SO,
02 Procesa las teclas recibidas 08 else: curses.KEY_DOWN):
03 if curses.ascii.isprint(ch): 09 if ch in (curses.ascii.DLE, 13 self.decr_pos_fechas()
04 for comando in self.comandos: curses.KEY_UP): 14 self.redibuja()
05 if comando[0] == chr(ch): 10 self.incr_pos_fechas() 15 self.refresca()
06 (getattr(self,comando[1]))() 11 self.redibuja() 16 return 1

W W W. L I N U X - M A G A Z I N E . E S PYTHON 61
LIBRERAS Curses

Listado 6: cuaderno.py
001 #!/usr/local/bin/python 057 self.decr_pos_fechas() 108
002 # -*- coding: ISO8859-1 -*- 058 self.redibuja() 109 else:
003 059 110 self.scr_fechas.addstr
004 import curses 060 self.refresca() (pos_x,1,fecha_temp, 0)
005 import curses.ascii 061 return 1 111
006 import curses.textpad 062 112 self.scr_fechas.addstr
(pos_x,len(fecha_temp),
007 import time 063 def fecha(self): self.datos[fecha],0)
008 import os.path 064 return time.strftime(%Y-%m-%d 113 pos_x = pos_x + 1
009 import string %H:%M)
114
010 import sys 065
115 self.refresca()
011 import almacen 066 def long_fecha(self):
116
012 067 caja_texto = 2 # el | izquierdo
y el | derecho 117 def editar(self):
013 class GUI: 118 Muestra un cuadro para
068 return len(self.fecha()) +
014 Interfaz con el usuario. caja_texto introducir el texto y lo guarda
015 069 119 if len(self.datos) == 0:
016 def __init__(self,datos): 070 def long_linea_texto(self): 120 return
017 071 return (80 - self.long_fecha()) 121 else:
018 self.registra_comandos() 072 122 self.banner( EDITANDO ! --
019 [control+g] guardar | [q] salir
073 def banner(self, texto): --)
020 self.datos = datos 074 Muestra el texto en la zona de 123 fechas = self.listado_fechas()
021 self.pos_fechas = banner
len(self.datos) - 1 124 texto = self.datos
075 self.scr_info.clear() [fechas[self.pos_fechas]]
022 076 self.scr_info.addstr(texto, 125 self.refresca()
023 self.genera_ventanas() curses.A_BOLD)
126
024 077 self.scr_info.refresh()
127 # Capturamos el nuevo texto.
025 self.banner(-- [n] nueva | [e] 078
editar | [q] salir --) 128
079 def dibuja_fechas(self):
026 129 ncols, nlineas = 25, 4
080 Genera el listado de fechas de
027 self.dibuja_fechas() la izquierda 130 uly, ulx = 15, 20
028 081 131 stdscr.addstr(uly-2, ulx, Usa
Ctrl-G para guardar.)
029 self.refresca() 082 self.scr_fechas.clear()
132 win = curses.newwin(nlineas,
030 083 pos_x = 3 ncols, uly, ulx)
031 def genera_ventanas(self): 084 133 curses.textpad.rectangle
032 Genera las ventanas iniciales 085 # 8 elementos por arriba y abajo (stdscr, uly-1, ulx-1, uly +
033 self.scr_fechas = stdscr. 086 min = self.pos_fechas - 8 nlineas, ulx + ncols)
subwin(23, 80, 0, 0) 087 max = self.pos_fechas + 8 134 stdscr.refresh()
034 self.scr_info = stdscr. 088 135 nuevo_texto=
subwin(1,80,23,0) curses.textpad.Textbox(win).
089 if max > len(self.datos): edit()
035
090 max = len(self.datos) 136 stdscr.refresh()
036
091 min = min + (max - 137
037 def registra_comandos(self): len(self.datos))
038 Almacena la letra y el comando 138 nuevo_texto =
092 if min < 0: nuevo_texto.replace(\n,)
asociado
093 max = max + ( -min) 139 # Guardamos el nuevo texto
039 self.comandos = [[d,bor-
rar], 094 min = 0 140 self.datos[fechas
040 [e,editar], 095 [self.pos_fechas]] =
096 if len(self.datos) > 0: nuevo_texto
041 [n,nueva_entrada],
097 # Marcamos con negrita la fecha 141
042 [q,salir],
sobre la que est el curso 142 self.banner(-- [n] nueva | [e]
043 [s,estado]] editar | [q] salir --)
098 # para ello iteramos escribi-
044 endo las fechas y cuando la 143
045 def ejecuta_comando(self, ch): 099 # encontramos le pasamos el 144 def borrar(self):
046 Procesa las teclas recibidas atributo de negrita 145 Elimina la entrada selec-
047 if curses.ascii.isprint(ch): 100 fechas = self.listado_fechas() cionada
048 for comando in self.comandos: 101 for fecha in fechas[min:max]: 146 if len(self.datos) > 0:
049 if comando[0] == chr(ch): 102 147 fechas = self.listado_fechas()
050 (getattr(self,comando[1]))() 103 fecha_temp = [+fecha+] 148 del self.datos
104 [fechas[self.pos_fechas]]
051 break
105 if fechas[self.pos_fechas] == 149 self.actualiza_pos_fechas()
052 else:
fecha: 150 self.redibuja()
053 if ch in (curses.ascii.DLE,
curses.KEY_UP): 106 # Hemos encontrado nuestra 151 self.refresca()
054 self.incr_pos_fechas() fecha!!! 152
055 self.redibuja() 107 self.scr_fechas.addstr 153 def redibuja(self):
(pos_x,1,fecha_temp ,
056 elif ch in (curses.ascii.SO, curses.A_BOLD | curses. 154 Redibuja la pantalla
curses.KEY_DOWN): A_UNDERLINE) 155 self.dibuja_fechas()

62 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Curses LIBRERAS

El objeto Almacen es pasado a GUI un terminal tiene 80 columnas de ancho y nmero fijo de entradas encima y debajo
como parmetro en su creacin. Y la 25 filas de alto. Dejaremos una fila abajo, de la nuestra. Como tenemos la posicin
misin de GUI no es otra que al de res- que ser la que usemos para mostrar de la entrada seleccionada, o resaltada,
ponder a los eventos que el usuario enve informacin. La 24 filas restantes se con un sencillo clculo podemos selec-
mediante un bucle infinito. encargarn de mostrar las entradas alma- cionar que entradas mostraremos.
cenadas. Las listas en Python tienen una funcio-
Dos ventanas nalidad que nos ser muy til. Usando la
Nuestro programa va a disponer de dos Desplazamiento por las entradas sintaxis lista[comienzo:fin] podemos
ventanas. La mayor har las veces de La ventana de datos nos permitir des- extraer los elementos entre comienzo y
tabln donde podemos ver las anota- plazarnos arriba y abajo por las entra- fin formando una nueva lista.
ciones realizadas por el momento. das. Cmo podemos conseguir recrear Simplemente tenemos que seleccionar
Podremos desplazarnos arriba y abajo por este movimiento? La solucin es captu- aquellos que estn a una distancia fija
l. Para indicar qu fecha es la que tene- rando las teclas de los cursores arriba del seleccionado y usarlos como comien-
mos seleccionada la distinguiremos ilumi- y abajo. Cuando una de ellas se pulse zo y fin.
nndola en negrita y subrayndola. incrementaremos o decrementaremos Podemos ver el cdigo que realiza esta
La segunda venta har las veces de una variable que establece la posicin accin en el mtodo dibuja_fechas(self)
barra de ayuda y estado. Cuando cambie- la entrada seleccionada en cada del GUI en el Listado 6.
mos el estado, por ejemplo al editar, se momento y volveremos a dibujar, o
reflejar ah. Es el mismo modo de trabajo escribir, la pantalla de datos. Pero no lo Textbox
del que hace gala VIM. haremos de cualquier forma. Python provee de una herramienta muy
Las ventanas deben partir la pantalla de Queremos que el efecto sea vistoso, as til para la edicin de textos dentro de
manera que no se solapen. La pantalla de que siempre intentaremos mostrar un curses. Desgraciadamente, a pesar de su

Listado 6: cuaderno.py (cont.)


156 self.refresca() 188 resultado = 0 224 cadena =
157 189 for f in fechas: [P:+str(self.pos_fechas)+]
+--[L:+str(len(self.datos))+
158 def refresca(self): 190 if f == fecha: ] + cadena
159 self.scr_fechas.refresh() 191 resultado = cont 225 self.banner(cadena)
160 192 break 226
161 def listado_fechas(self): 193 cont += 1 227 class Diario:
162 Devuelve el listado de fechas 194 228 def __init__(self):
ordenado 195 self.pos_fechas = resultado 229 self.datos = almacen.Alma-
163 fechas = self.datos.entradas() 196 cen(diario)
164 fechas.sort(reverse=-1) 197 self.redibuja() 230 self.gui = GUI(self.datos)
165 return fechas 198 self.refresca() 231 self.bucle_infinito()
166 199 232
167 def decr_pos_fechas(self): 200 def nueva_entrada(self): 233 def bucle_infinito(self):
168 Mueve la fecha seleccionada 201 Introduce una nueva fecha 234 while 1:
hacia arriba
202 fechas = self.listado_fechas() 235 c = stdscr.getch()
169 if self.pos_fechas >=
(len(self.datos) - 1): 203 fecha = self.fecha() 236 n = self.gui.ejecuta_comando
204 if not fecha in fechas: (c)
170 self.pos_fechas =
len(self.datos) - 1 205 self.datos[fecha] = Texto 237 if not n:
171 else: vacio 238 break
172 self.pos_fechas += 1 206 self.actualiza_pos_fechas 239
173 207 self.redibuja() 240 def arranca_curses(stdscr):
174 def incr_pos_fechas(self): 208 self.refresca() 241 curses.noecho()
175 Mueve la fecha seleccionada 209 self.editar() 242 curses.cbreak()
hacia abajo 210 243 stdscr.keypad(1)
176 if self.pos_fechas <= 0: 211 def salir(self): 244
177 self.pos_fechas = 0 212 cierra_curses(stdscr) 245 def cierra_curses(stdscr):
178 else: 213 sys.exit(0) 246 stdscr.keypad(0)
179 self.pos_fechas -= 1 214 247 curses.echo()
180 215 def estado(self): 248 curses.nocbreak()
181 def actualiza_pos_fechas 216 Muestra informacin general 249 curses.endwin()
(self,fecha=): 217 cadena = 250
182 Realiza los cambios oportunos 218 fechas = self.listado_fechas() 251 if __name__==__main__:
cuando se elimina una fecha
219 cont = 0 252 stdscr=curses.initscr()
183 if fecha == :
220 for fecha in fechas: 253 arranca_curses(stdscr)
184 self.decr_pos_fechas()
221 cadena += 254 diario = Diario()
185 else: [+str(cont)+>+fecha+] 255 cierra_curses(stdscr)
186 fechas = self.listado_fechas() 222 cont += 1
187 cont = 0 223

W W W. L I N U X - M A G A Z I N E . E S PYTHON 63
LIBRERAS Curses

potencia posee algunos inconvenientes de tidad de ifs anidados, cada uno de los car. De esta manera, nuestro gestor de
los que hablaremos ms tarde. cuales responde ante una tecla o combi- comandos se reduce a un cdigo que reci-
Esta herramienta es el objeto Textbox nacin distinta. El cdigo generado llegar be un carcter, lo compara con el primero
que se encuentra en la librera curses.text- a convertirse en ilegible en cuanto el elemento de cada entrada en su lista de
pad. Textbox nos permite editar un texto nmero de comandos sobrepasa los diez. comandos y si encuentra una coinciden-
dentro de una ventana y poder utilizar Hay una manera mucho ms elegante cia ejecuta el comando asociado. Ver
muchas de las combinaciones de teclas de atacar este problema, pero no es tan Listado 5.
que soporta EMACS. As por ejemplo con fcil hacer uso de ella en todos los lengua- Como podemos ver en el cdigo, se
control+e iremos al final de la linea jes. Afortunadamente Python nos permite comprueba si el carcter recibido es
que estemos editando y con control+d una implementacin muy sencilla. La imprimible y posteriormente se busca en
borraremos el carcter sobre el que nos idea es la siguiente: Cada comando estar la lista de comandos. En caso de coinci-
encontremos. asociado a una serie de acciones a reali- dencia se ejecuta usando como instancia
Utilizaremos un Textbox para recoger el zar. Englobaremos las acciones vincula- self. De esta manera, es posible manipular
texto que el usuario quiera introducir. das con cada comando a un mtodo de el funcionamiento ante qu caracteres res-
Desgraciadamente posee una limitacin nuestro objeto GUI. Hasta aqu todo es ponde el programa sin tener que modificar
debido su diseo: Si cuando estamos edi- bastante normal. Ahora viene la magia. el cdigo fuente. Esto nos da mucha flexi-
tando el texto, pulsamos repetidas veces Python nos permite invocar mtodos de bilidad y es menos propenso a errores.
el cursor izquierdo desplazndonos hasta objetos usando su nombre. Si declaramos
dar con el borde de la ventana, el progra- el objeto persona: Uso del Programa
ma fallar. El uso del programa se ha hecho lo ms
El soporte de curses de Python se basa >>> class Persona: simple posible, el aspecto del mismo se
en las libreras originales escritas en C, y ... def habla(self): puede ver en la Figura 4. Cuando se pulsa
como ya hemos dicho son de muy bajo ... print "hola mundo" n se crea una nueva entrada con la
nivel. La implementacin de Textbox es ... fecha y la hora, si existe ya una entrada
realmente bsica y no controla todas las >>> con la fecha y la hora no se hace nada.
circunstancias. An as servir. Con d se elimina una entrada y con e
Un ejemplo de utilizacin de Textbox Podemos invocar el mtodo habla usando se edita. Cuando se est introduciendo un
aparece en su propio cdigo fuente, ver una cadena con su nombre mediante el texto, al pulsar control+g se guarda.
Listado 4 y Figura 2. Este cdigo genera mtodo getattr(), que precisa de la instan- Para salir se pulsa q y con los cursores
un rectngulo con bordes (usando la fun- cia del objeto y el mtodo a invocar. arriba y abajo nos desplazamos por la
cin rectangle de curses.textpad) y nos Devuelve, por as decirlo, una referencia lista.
solicita que escribamos algo en el mismo. al mtodo en cuestin que funciona de la Al fichero de almacenado se le ha dado
Para acabar debemos pulsar control+g, misma manera. Como dicen por ah, una el nombre diario.db. Si no existe se
mostrndonos lo escrito ms abajo. Si lo imagen vale ms que mil palabras: crea, y si existe se emplea el existente.
probamos comprobaremos que no pode-
mos salir del rectngulo al editar. >>> pepe = Persona() Conclusin
En la Figura 3 vemos como hemos inte- >>> pepe.habla() Aunque el uso de Curses puede resultar
grado el Textbox en nuestro programa. hola mundo engorroso, Python nos provee de una
Hemos aumentado el nmero de colum- >>> (getattr(pepe, "habla"))() librera que las manipula dentro su insta-
nas para permitir introducir mensajes hola mundo lacin base. Una vez realizado el progra-
ms largos. >>> ma sabremos que cualquiera que instale
Python podr hacer uso de l.
El Gestor de Comandos Lo que haremos ser crear una lista de lis- Siempre es posible realizar una serie de
Existen muchas maneras de hacer un ges- tas, cada una de las cuales contendr dos objetos que realicen tareas de ms alto
tor de comandos. La ms tpica consiste elementos. El primero ser un carcter y nivel. Existen libreras que nos proporcio-
en hacer una sentencia switch o gran can- el segundo el nombre del mtodo a invo- nan barras de mens y widgets ms avan-
zados. An as, siempre es bueno estar lo
Tabla 1: Algunos mtodos especiales de Python ms cerca posible del estndar.
Mtodo Descripcin La prxima vez que tengas que hacer
un interfaz en modo texto puede que sea
__len__(self) devuelve la longitud de nuestro objeto. Se invoca cuando se ejecuta
len(miObjeto) una buena idea darle una oportunidad a
curses.
__setitem(self, clave,valor) se corresponde con la asignacin en un diccionario:
miObjeto[Algo] = otra cosa
Recursos
__getitem(self, clave) es el equivalente miObjeto[Algo] y devuelve la informacin alma-
[1] Descargas de los listados de este art-
cenada en Algo.
culo: http://www.linux-magazine.es
__delitem(self, clave) es del miObjeto[Algo] y se corresponde con la eliminacin de esa /Magazine/Downloads/Especiales/06_
entrada. Python

64 PYTHON W W W. L I N U X - M A G A Z I N E . E S
3D con VTK LIBRERAS

realizando. Por un momento, imaginad


Visualizacin 3D con VTK (Visualization Toolkit)
que estis en la butaca del cine, viendo

Grficas 3D
una pelcula de animacin, como por
ejemplo La Edad de Hielo. Si nos cen-
tramos en una nica escena y la describi-
mos, vemos personajes animados
(actores), luces de diferentes tonali-
dades, cmaras que modifican el punto
de vista, propiedades de los personajes
(color, forma, etc.). Aunque no lo creis,
Hoy por hoy, la representacin grfica 3D y su visualizacin forman todos estos conceptos son la base de la
visualizacin grfica. Veamos dicha
parte de nuestra vida cotidiana; basta fijarse en el mundo del estructura.
El toolkit de visualizacin VTK est
entretenimiento, en la industria del juego y en el soporte de hard- diseado a partir de dos modelos clara-
mente diferenciables: el modelo grfico y
ware y software para tales fines. Quin en su ordenador personal el modelo de visualizacin.
Modelo grfico. El modelo grfico
no ha instalado un juego o visto una pelcula renderizada en 3D? captura las principales caractersticas
de un sistema grfico 3D, de un modo
Por Ana M. Ferreiro Ferreiro y Jos A. Garca Rodrguez
fcil de entender y usar (ver Figura
1). La abstraccin se basa en la indus-
tria del cine. Los objetos bsicos que
La representacin grfica en 3D ofrece trial, reconstruccin de superficies a par- constituyen este modelo son: vtkRen-
la posibilidad de crear mundos virtuales tir de digitalizacin lser o nubes de derer, vtkRenderWindow, vtkLight,
en un ordenador, lo cual, unido a la puntos desorganizados, etc. vtkCamera, vtkProp, vtkProperty,
visualizacin permite al usuario explorar En lo que sigue veremos los conceptos vtkMapper, vtkTransform. En la Tabla
y entender, rpidamente, sistemas com- bsicos en los que se basa VTK para 1 se describen cada uno de estos obje-
plicados. Esto es posible gracias al poder generar una escena y, mediante tos.
avance de lenguajes orientados a obje- una serie de ejemplos desarrollados en Modelo de visualizacin. El papel del
tos, que ofrecen la posibilidad de crear Python, llegaremos a crear nuestras modelo grfico es transformar datos
software de mejor calidad y ms fcil de propias escenas de visualizacin. grficos en imgenes, mientras que el
mantener. del modelo de visualizacin trans-
Entre las diferentes herramientas de Instalacin forma informacin en datos grficos;
visualizacin, representacin 3D y Para poder realizar todas las pruebas que esto significa que el modelo de visual-
procesamiento de imgenes, cabe se van sugiriendo y las que se os ocu- izacin es el responsable de construir
destacar VTK (Visualization Toolkit), rran, es necesario tener instalado Python la representacin geomtrica que se
cdigo abierto cuyo ncleo est imple- y VTK con soporte para Python. Adems, renderiza mediante el modelo grfico.
mentado en C++ y que soporta la tarjeta grfica de nuestro ordenador VTK se basa en la aproximacin de los
envolturas (wrappers) para TCL, debe tener OpenGL funcionando. datos para transformar la informacin
Python y Java, permitiendo el desarrollo La instalacin de las libreras VTK en datos grficos. Hay dos tipos bsi-
de aplicaciones complejas de un modo (que no suelen estar instaladas de man- cos de objetos, descritos en la Tabla 2,
eficiente y mediante scripts sencillos. Por era predeterminada) es muy sencilla. involucrados en dicha aproximacin:
todo ello, VTK se emplea en la visua- Todas las distros mayoritarias cuentan vtkDataObject y vtkProcessObject.
lizacin mdica, la visualizacin indus- con los paquetes necesarios en sus
repositorios.
En Debian o Ubuntu, por ejemplo,
bastar con ejecutar

apt-cache search vtk

para ver las que necesitamos.

Modelos de Objetos VTK


Para los inexpertos en el mundo de la
visualizacin, vamos a explicar de un
modo sencillo la estructura de VTK, ya Figura 2: Tipos de datos: a) datos poligo-
que esto permite que comprendamos nales, b) puntos estructurados c) malla no
Figura 1: Estructura del modelo grfico. mejor cada uno de los pasos que iremos estructurada d) malla estructurada.

W W W. L I N U X - M A G A Z I N E . E S PYTHON 65
LIBRERAS 3D con VTK

Los diferentes tipos de datos que pueden ren=vtk.vtkRenderer() sus botones de minimizar, maximizar y
constituir un objetos son, entre otros, renWin=vtk.vtkRenderWindow() cerrar; y que slo se cierra cuando el
puntos, rectas, polgonos, puntos estruc- renWin.AddRenderer(ren) usuario lo estima oportuno (Figura 3).
turados, mallas estructuradas y no iren=vtk.U Esta ventana va a ser el contenedor de
estructuradas, etc. (ver Figura 2). vtkRenderWindowInteractor() nuestra pequea escena. Ntese que las
iren.SetRenderWindow(renWin) dos lneas de cdigo que acabamos de
Mi Primera Escena escribir deben de estar al final del
Ya estamos preparados para construir Para poder manipular la cmara median- fichero. Las dems lneas que escribamos
nuestra primera escena. Situaros en el te el ratn se ha instanciado el objeto a partir de este momento debemos situar-
papel de director de cine. En los si- vtkRenderWindowInteractor (denomi- las justo antes.
guientes ejemplos veremos el modo de nado en el cdigo como iren). Ntese Para crear nuestro primer actor no nos
emplear las clases que acabamos de que la ventana de renderizado renWin se vamos a complicar demasiado, porque
describir. Para ello, tal como se men- asocia al objeto de interaccin iren medi- ya queremos ver algo. VTK contiene una
ciona al comienzo, instanciaremos VTK ante el mtodo SetRenderWindow. En serie de clases que nos permiten crear
desde Python. este momento no se aprecia la utilidad objetos tridimensionales sencillos, como
Con cualquier editor de textos, del mismo, paciencia ya compren- son: esfera (vtkSphereSource), cono (vtk-
creamos el fichero cone.py. Lo primero deris su importancia cuando tengamos ConeSource), cilindro (vtkCilinder-
es importar desde Python el paquete un actor en nuestra escena. Source), etc. Para nuestro ejemplo hemos
VTK; esto es tan sencillo como escribir la Guardamos el fichero y en la lnea de escogido un cono, sin embargo, puedes
siguiente lnea: comandos ejecutamos el programa tecle- optar por cualquiera de los otros objetos.
ando python cone.py No ocurre nada! El siguiente cdigo nos permite crear
import vtk Esto es porque debemos inicializar la nuestro primer actor,
interaccin del usuario e indicar que la
Ahora que ya podemos instanciar ventana de renderizado permanezca visi- cone=vtk.vtkConeSource()
cualquier objeto de VTK, sin ms que ble hasta que el usuario finalice la ejecu- coneMapper=vtk.U
escribir vtk.nombre_clase, necesitamos cin de la misma cerrndola. Para ello vktPolyDataMapper()
crear nuestra ventana de renderizado basta escribir coneMapper.SetInput(cone.U
vtk.vtkRenderWindow, a la que llamare- GetOutput())
mos renWin y a la que asociamos un iren.Initialize() coneActor=vtk.vtkActor()
rea de renderizado vtk.vtkRenderer (que iren.Start() coneActor.SetMapper(coneMapper)
denominamos ren), mediante el mtodo
AddRenderer(). Escribamos las siguien- Si ejecutamos nuevamente el programa, Mediante el objeto vtk.vtkConeSource
tes lneas de cdigo: se abre una ventana de color negro con creamos una representacin poligonal

Listado 1: cono_esfera.py
01 import vtk 18 esferaActor.SetMapper 34 #Fijamos el color de fondo, el
02 (esferaMapper) tamao y hacemos zoom sobre
03 # Generamos la estructura para 19 esferaActor.GetProperty().Set- 35 #el area de Renderizado
ver un cono Color(0.7,0.0,0.25) 36 ren.SetBackground(1, 1, 1)
04 cone = vtk.vtkConeSource() 20 esferaActor.GetProperty(). 37 renWin.SetSize(450, 425)
05 coneMapper = vtk.vtk SetOpacity(0.75) 38 camera=ren.GetActiveCamera()
PolyDataMapper() 21 esferaActor.GetProperty(). 39 ##camera.Zoom(1.5)
06 coneMapper.SetInput(cone. SetLineWidth(1) 40
GetOutput()) 22 41 coneActor.RotateX(30)
07 coneActor = vtk.vtkActor() 23 # Creamos: Renderer, Render 42 coneActor.RotateY(45)
08 coneActor.SetMapper(coneMapper) Window, RenderWindowInteractor 43 conepro=coneActor.GetProperty()
09 24 ren = vtk.vtkRenderer() 44 conepro.SetColor(0,0.6,1)
10 # C r e a r f u e n t e d e e s f e r a , 25 renWin = vtk.vtkRenderWindow() 45 ##conepro.SetOpacity(0.5)
mapeador y actor 26 renWin.AddRenderer(ren) 46 conepro.SetLineWidth(2)
11 esfera = vtk.vtkSphereSource() 27 iren = vtk.vtkRenderWindow 47 ren.ResetCamera()
12 esferaMapper = vtk. Interactor() 48 ##camera=ren.GetActiveCamera()
vtkPolyDataMapper() 28 iren.SetRenderWindow(renWin) 49 camera.Zoom(1.5)
13 esfera.SetPhiResolution(10) 29 50
14 esfera.SetThetaResolution(20) 30 # Aadimos el actor en el rea 51 cone.SetResolution(40)
15 esfera.SetCenter(0.3,0.0,0.0) de renderizado (Renderer) 52
16 esferaMapper.SetInput(esfera. 31 ren.AddActor(coneActor) 53 iren.Initialize()
GetOutput()) 32 ren.AddActor(esferaActor) 54 renWin.Render()
17 esferaActor = vtk.vtkActor() 33 55 iren.Start():

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 vtkProp objeto que representa los diferentes elementos (actores) que se sitan den-
seguir para crear un actor. tro de la escena. Caben destacar las siguientes subclases: vtkActor, vtkVol-
ume, vtkActor2D.
de un cono, que hemos llamado cone. vtkProperty representa los atributos de renderizado de un actor, incluyento color, ilumi-
La salida del cono (cone.GetOutput()) nacin, mapeado de la estructura, estilo de dibujo y estilo de la sombra.
es un conjunto que se asocia al map-
vtkMapper representa la definicin de la geometra de un actor y mapea los objetos
per (coneMapper) (vtk.vtkPoly-
mediante una tabla de colores (vtkLookupTable). El mapper proporciona
DataMapper) va el mtodo SetInput(). la frontera entre el modelo de visualizin y el modelo grfico.
Creamos el actor (objeto que se va ren-
derizar) al que se le asocia la repre- vtkTransform objeto consistente en una matriz de transformacin 4x4 y mtodos para
modificar dicha matriz. Especifica la posicin y orientacin de actores,
sentacin geomtrica que aporta
cmaras y luces
coneMapper. Ntese que los pasos aqu
indicados son los que, en general, nece-
sitamos seguir para poder construir un Tabla2: Modelo de Visualizacin
actor (Figura 4). Objeto Descripcin
Cuando creamos un actor, no se
vtkDataObject clase genrica que permite representar diferentes tipos de datos. Los obje-
incluye por defecto en la escena. Es tos de datos consisten en estructuras geomtricas y topolgicas (puntos y
necesario aadirlo al Renderer mediante celdas), y tambin en atributos asociados, tales como escalares o vectores.
AddActor, y posteriormente renderizar la
vtkProcessObject objeto que hace referencia a filtros, que actan sobre los actores modificn-
escena. Esto se logra escribiendo, dolos.

ren.AddActor(conoActor)
renWin.Render() Prueba a comentar la lnea Habrs observado que la ventana de
renWin.Render(). Qu ocurre? Como renderizado se abre con un tamao pre-
Si volvemos a ejecutar, visualizamos un habrs podido observar el cono ya no determinado. Para fijar el tamao de
cono de color gris (color que se muestra aparece, esto es porque cada vez que dicha ventana es necesario emplear el
por defecto) dentro de nuestra ventana aadimos un actor es necesario ren- mtodo SetSize, donde indicamos el alto
(Figura 5). Adems, es en este instante derizar la escena, ya que de lo contrario y el ancho en pixels,
cuando se aprecia la interaccin con el no se realiza un refresco de la misma y
ratn; con el botn izquierdo puedes es como si no hubisemos aadido un renWin.SetSize(450,325)
rotar la cmara, el botn central permite nuevo actor.
trasladarla, y con el botn derecho nos
acercamos o alejamos del objeto. Propiedades de Objetos
Adems, habrs observado que en la Si has seguido el tutorial hasta este
escena, por defecto se incluye una luz punto, habrs creado tu cono de color
para poder visualizar los objetos ilumi- gris. Pero probablemente no ests
nados. demasiado satisfecho, porque todos te-
nemos 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 Figura 6: Comportamiento de los mtodos de
renderizado, la cmara, la cmara. a) Azimuth - flechas rojas; b)
propiedades del actor, etc. Al Pitch - flechas azul celeste; c) Yaw - flechas
final, podrs realizar todos azul oscuro; d) Elevation - flechas verdes; e)
Figura 4: Ventana de ren- Figura 5: Cono dentro de aquellos cambios que te Roll - flecha amarilla. La esfera blanca repre-
derizado por defecto. la escena. apetezcan. senta el foco.

W W W. L I N U X - M A G A Z I N E . E S PYTHON 67
LIBRERAS 3D con VTK

Si lo que pretendemos es cambiar el mezclando distin-


color de fondo de la escena (vtkRen- tos mtodos de
derer) empleamos el mtodo SetBack- rotacin y uno as
ground (RGB), donde le pasamos el color no sabe realmente
deseado en formato RGB. Si queremos lo que ocurre. Si
un fondo azul bastara escribir en algn
momento el actor
ren.SetBackground(0.0, 0.0, 1) desaparece de la
escena no te pre-
Como dijimos, el rea de renderizado ocupes, lo que
(vtkRenderer) coordina la cmara y las est sucediendo Figure 7: Vista de la superficie Figura 9: Escena con dos actores
luces. Mediante el mtodo GetActive- es que el ngulo del cono. que se intersecan.
Camera() se accede a la cmara creada de rotacin ha
en la escena, as podemos aplicarle todos colocado la cmara justo en un punto La lnea conepro.SetResolution(40)
los mtodos del objeto vtkCamera para que evita que visualicemos el objeto modifica la resolucin con la que se ren-
poder modificar la visualizacin segn dentro de la escena. En la Figura 6 se deriza el cono. Este mtodo no es general
queramos. Si lo que pretendemos es que explica de un modo sencillo el modo en para todos los actores, sino para ciertos
todos nuestros actores se vean en su que actan cada uno de estos mtodos objetos que VTK ya incluye, como son:
totalidad dentro del rea de renderizado, respecto del foco (representado por una esfera (vtkSphereSource), cono (vtkCone-
es necesario llamar al mtodo ResetCa- esfera blanca). Source), cilindro (vtkCilinderSource), etc.
mera(). En las siguientes lneas se reco- A partir de este punto comentad las Cualquier objeto puede rotarse,
gen algunos de los mtodos relativos a la lneas de cdigo correspondientes a los escalarse, obtener su dimensiones, etc.,
cmara mtodos que actan sobre la cmara, utilizando las propiedades de un vtkActor
dejando nicamente la lnea came- en particular(si queris ms informacin,
ren.ResetCamera() ra.Zoom(1.5). As vamos viendo cada basta consultar las ayuda de VTK sobre
camera=ren.GetActiveCamera() cosa por separado, despus ya tendris vtkProp3D, que es la clase padre). Para
camera.Azimuth(60) tiempo de mezclar cdigo. rotar nuestro cono y escalarlo basta
camera.Pitch(5) Ahora que sabemos modificar la escribir
camera.Yaw(5) escena, debemos recordar que el cono
camera.Roll(50) contina vindose en un color gris un coneActor.RotateX(30)
camera.Elevation(20) poco apagado. Para acceder a las coneActor.RotateY(45)
camera.Zoom(1.5) propiedades de cualquier actor vtkActor coneActor.SetScale([1,3,2])
se emplea el mtodo GetProperty(), que
Los mtodos Azimuth, Pitch, Yaw, devuelve una instancia del objeto Ahora ya sabis crear vuestra propia
Roll,Elevation se ocupan de rotar la vtkProp asociado a dicho actor. Las escena, modificar sus propiedades,
cmara o el punto de foco en diferentes siguientes lneas permiten modificar el aadir un actor con las opciones que
direcciones y, como argumento, se pasa color, la transparencia y grosor de las queris y modificar la cmara. En caso
un ngulo de rotacin. Lo mejor es que lneas: de que quisierais aadir ms actores a
juegues un poco con la cmara y veas lo vuestra ventana de renderizado, basta
que ocurre probando cada uno de estos conepro=coneActor.GetProperty() seguir el mismo procedimiento que
mtodos por separado. Por ejemplo, para conepro.SetColor(1,0.2,0) hemos empleado para crear nuestro
ver cmo afecta el mtodo Azimuth apli- conepro.SetOpacity(0.5) cono. En el Listado 1 se aade a la
cado a la cmara, comenta las restantes conepro.SetLineWidth(3) escena un cono y una esfera que se inter-
lineas de cdigo, por que si no estaras conepro.SetResolution(40) secan (Figura 9).
conepro.U
SetRepresentationToWireframe() Recursos
[1] RPMs VTK de Mandrake:
La ltima lnea de este cdigo se indica ftp://ftp.rediris.es/sites3/carroll.cac.
que queremos ver la estructura bsica psu.edu/mandrakelinux/official/10.1/
que constituye el actor, es decir, el ma- i586/media/contrib/
llado. Por defecto, VTK tiene asociadas [2] Kitware. VTK:
teclas rpidas a la escena: si tecleas la http://www.kitware.org
letra s se ven todos los objetos rende-
[3] Enthought. Scientific python:
rizados (ver Figura 7), mientras que si http://www.scipy.org
tecleas la letra w, se visualiza slo la
[4] MayaVi: http://mayavi.sourceforge.net
malla (ver Figura 8). Ahora apreciars
[5] Cdigo de este artculo: http://www.
mejor la diferencia entre mallado y
linux-magazine.es/Magazine/Down-
estructura renderizada, no hay nada
loads/Especiales/06_Python
Figura 8: Vista de la malla del cono. mejor que poder ver las cosas.

68 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 posible recibir en tu propia casa imge- permita recoger y mantener actualizadas
un cohete, todos quedamos asombrados nes fascinantes del universo, de Marte o las imgenes que queremos en una espe-
ante la explosin del despegue, la atenta de la Tierra. cie de collage o mural. Construiremos
mirada de todos esos cientficos a los La temperatura del ocano, imgenes nuestro propio centro de control espa-
paneles de control, y la monstruosa cifra meteorolgicas, imgenes del campo cial.
que nos dicen que se han gastado en el magntico del sol o de las misiones a
proyecto. Marte son enviadas constantemente a la Recoger las Imgenes
Tierra desde estos engendros espaciales. Lo primero ser encontrar las imgenes y
Donde Van los Impuestos? Y el efecto es siempre el mismo, el espec- reunirlas. Vamos a usar como ejemplo
Es entonces cuando surge la pregunta y tador es deslumbrado por el presentador cuatro imgenes de carcter cientfico.
eso a m en qu me repercute? Un da, de televisin con unas imgenes incre- Se actualizan a distintos intervalos, de
estando en el despacho de la Rama del bles mientras se escuchan acordes de manera que podremos ver cmo evolu-
IEEE de Mlaga tuve una conversacin sintetizador. cionan las eventos que se registran.
en la que me contaron que la mayor Acaso no son esas imgenes de domi- Puedes encontrar las URLs en Recursos
parte de los satlites emiten al mundo nio pblico? Dnde puedo conseguirlas? [1].
las imgenes y los datos que recogen. Es En el presente artculo utilizaremos Debemos descargar las imgenes y
decir, si se posee el equipo necesario es Python para crear un script CGI que nos almacenarlas dentro de nuestro progra-

W W W. L I N U X - M A G A Z I N E . E S 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 Figura 2: La imagen del pequeo demonio de
modificar con PIL. >>> c.request("GET","/issue/08") BSD rotado 45 con PIL.
>>>
ma; haremos uso de la librera httplib, Spamassasin, Hypermail,U
que es parte de la distribucin estndar Usamos el comando GET, con lo que Encriptacin GPG, SDL,U
de Python. Esta librera nos permitir estamos solicitando un objeto. El segun- ...
hablar de t a t con un servidor web do parmetro del mtodo es la ruta
remoto sin tener que preocuparnos de hasta el objeto. As que la URL que esta- Cuando hayamos finalizado debemos
los detalles de ms bajo nivel. Esta con- mos solicitando es http://www. cerrar la conexin invocando el mtodo
versacin la realizaremos usando el pro- linux-magazine.es/index.html. Es impor- close() del objeto que representa la cone-
tocolo HTTP. Este protocolo es bastante tante que la ruta comience con una barra xin, en este caso sera:
simple, y de l solo necesitaremos una /, como si fuese la ruta de un fichero
parte mnima. de una mquina. Cmo sabemos si todo >>> c.close()
Cuando Tim Berners Lee realiz el ha ido bien? >>>
diseo original de la Web, quiso que el
protocolo para pedir los documentos >>> r = c.getresponse() Para obtener las imgenes vamos a hacer
fuese lo ms simple posible. HTTP se >>> print r.status,r.reason exactamente lo mismo, abriremos una
reduce a la recepcin y el envo de infor- 200 OK conexin, pediremos la imagen, la alma-
macin al servidor, eso y slo eso. Se >>> cenaremos en un diccionario y cerrare-
compone de varios comandos, pero los mos la conexin.
ms conocidos son GET, que podemos Con getresponse podemos conseguir un
traducir como tomar, y POST, que objeto que representa los datos devuel- Python Imaging Library
podemos traducir en este contexto como tos por la conexin. Este objeto tiene, Nuestra idea original era realizar un
enviar o mandar. As que tomamos entre otros, los atributos status y reason mural o collage con las imgenes recupe-
documentos y enviamos informacin. que nos indican el estado, un nmero radas. Python no nos provee de una
Una parte importante de HTTP es URL, con un significado especial, y la explica- librera de tratamiento grfico en su dis-
que nos sirve para darle nombre a esos cin del mismo. En este caso todo ha ido tribucin estndar. Eso no quiere decir
documentos. Todos estamos acostumbra- bien, y por eso recibimos un OK. En que no exista tal librera, ya que no slo
dos a tratar con urls, generalmente del caso contrario, si no existiese la ruta que existe, sino que adems es muy potente
tipo http://www.linux-magazine.es/ pedimos habramos obtenido: y til. Nos referimos a Python Imaging
issue/08. La url se compone de: [protoco- Library (ver URL [2] en el Listado de
lo]://[maquina]/[ruta]/[objeto]. Vamos la >>> r = c.getresponse() Recursos al final del artculo).
ver ahora porqu es tan importante que >>> print r.status, r.reason La librera PIL (Python Imaging
sepamos esto. 400 Bad Request Library) nos va a permitir tratar imge-
>>>
Curiosidad Listado 1: Ejemplo Uso de PIL
Ahora ya tenemos la pgina, slo tene-
Poco tiempo despus de finalizar este 01 >>>mural =
mos que leerla-- usando el mtodo
artculo apareci una noticia en Slashdot Image.new('RGB',(600,480))
(ver Recursos [4]) hablando de una lla- read() del objeto respuesta.
02 >>> im =
marada solar de tal tamao que iba a Image.open("daemon.jpg")
alterar las comunicaciones. Cuando se >>> print r.read()
03 >>> im.thumbnail((300,200),
dan este tipo de eventos en muchos cen- <html>
Image.ANTIALIAS)
tros de control de satlites, los ingenie- <head>
04 >>> mural.paste(im,(0,0))
ros cruzan los dedos para que sus satli- <base href="http://www.U
05 >>> mural.paste(im,(300,0))
tes no caigan ante la ola de viento solar linux-magazine.es/issue/08/" />
06 >>> mural.show()
que se origina. El lector puede apreciar
la llamarada en la Figura 4. 07 >>>
<title>Linux Magazine -U

70 PYTHON W W W. L I N U X - M A G A Z I N E . E S
PIL LIBRERAS

Para hacerlo vamos a insertar las im-


genes 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 recua-
dros como imgenes vayamos a insertar.
Cmo sabremos la cantidad de cuadr-
culas? Pues escogeremos la menor
potencia de 2 que sea mayor que nuestro
nmero de imgenes. No es muy compli-
cado; por ejemplo, si tenemos 7 imge-
nes, 8 (2 elevado a 3) ser suficiente.
Figura 3: Creamos una imagen vaca con PIL Bsicamente multiplicaremos 2 por s Figura 4: Llamaradas solares que amenazan
y despus colocamos otras imgenes en su mismo hasta que sea mayor que el con dejar fuera de combate a los satlites de
interior como mosaico. nmero de imgenes que queramos mos- comunicadciones.
trar. Grficamente lo que haremos ser ir
nes en una gran cantidad de formatos. dividiendo en anchura y en altura la ima- El Fichero de Configuracin
Podremos convertirlas a otro formato, gen en cuadrculas, en cada iteracin se Las URLs y la resolucin deben ser reco-
rotarlas, escalarlas, mezclarlas, etc. multiplicar por 2 el nmero de cuadr- gidas por el programa, pero cmo?
Aquel lector que haya tenido contacto culas. Con este mtodo perderemos Existen varias opciones-- podramos
con programas de manipulacin grfica, espacio en la imagen, pero al ser tan sen- pasrselas al programa cuando se ejecu-
como por ejemplo GIMP (ver URL [3] en cillo no complicar mucho el cdigo. te. Las URLs tienen el problema de ser
el Listado Recursos), comprender la bastante largas en ocasiones, as que la
potencia de una librera con estas funcio- Creemos el thumbnail linea de comandos para ejecutar el pro-
nalidades. Primero creemos una imagen vaca, ver grama puede ser engorrosa.
Como no viene de serie con Python, Listado 1. La Figura 3 muestra el resulta- En lugar de eso vamos a usar un fiche-
deberemos instalarla en nuestra distribu- do. En la imagen vaca que creamos esta ro de configuracin. Cada vez que el pro-
cin o sistema operativo. Existen paque- vez no cargamos ninguna imagen, sino grama se ejecute, leer este fichero y
tes RPM y DEB de la misma. que usamos el mtodo new() que necesi- recoger los parmetros oportunos.
Cmo se trabaja con PIL? Pues ta el tipo de pixel (RGB viene de Red- Qu forma tendr el fichero? La lti-
mediante la manipulacin de objetos de >Rojo Green->Verde Blue->Azul, es ma tendencia es crear ficheros XML de
la clase Image. Esta clase es capaz de uno de los formatos estndar) y el tama- configuracin. Pero el XML puede ser
albergar imgenes de casi cualquier for- o de la imagen medido en pixels. En demasiado complicado si tenemos en
mato, permitindonos manipularlas. nuestro caso hemos escogido 600 pixels cuenta que nuestro fichero de
Vemos un ejemplo. En la Figura 1 pode- de ancho por 480 de alto (presta atencin configuracin puede no tener ms de 10
mos ver la imagen original del fichero a los (), porque la resolucin se expre- lneas. En UNIX, la tendencia es la de
daemon.jpg en mi equipo. Vamos a rotar- sa como una secuencia del tipo (x,y) ). usar el formato de clave = valor, y ese
la 45 grados: Esta nueva imagen no contiene nada, a es el que usaremos. El fichero ser como
excepcin de un decepcionante fondo el que se muestra en el Listado 2.
>>> import Image negro. Vamos a poner algo de color! Leeremos cada lnea del fichero, la
>>> im = Image.openU Cogemos la imagen del daemon e dividiremos usando el = y usaremos
("daemon.jpg") invocamos el mtodo thumbnail(), que
>>> img.rotate(45).show() escala la imagen tanto vertical como Listado 2: collage.conf
>>> horizontalmente. Tenemos que pasarle el 01 [tamao]
tamao deseado como una secuencia; la 02 horizontal = 800
En la Figura 2 podemos ver el resultado. nueva imagen tendr un tamao de 03 vertical = 600
Hemos usado el mtodo rotate(), al que 300x200 pixels. Puede aceptar un par- 04
hemos pasado un ngulo de 45 grados, y metro adicional, en nuestro caso es 05 [imgenes]
en el resultado hemos invocado el mto- Image.ANTIALIAS, que debera mejorar 06 url1 = http://www-mgcm.arc.
do show() que mostrar el resultado la resolucin de la nueva imagen. nasa.gov/MarsToday/marstoday.
mediante el programa xv (para cerrar xv A continuacin usamos el mtodo gif
slo tenemos que pulsar q). paste() de Image, que nos permite 07 url2 = http://www.sec.noaa.
Nosotros no buscamos rotar imgenes, pegar una imagen dentro de otra en las gov/sxi/current_sxi_4MKcorona.
sino escalarlas. Las imgenes presentes coordenadas indicadas como segundo png
en la web suelen ser de gran tamao, y parmetro. Pegamos la imagen dae- 08 url3 = http://www.ssec.wisc.
nosotros queremos crear un mural de un mon dos veces, la primera en la posi- edu/data/sst/latest_sst.gif
tamao esttico. Tendremos que adaptar cin (0,0) del mural y la segunda en la 09 url4 = http://www.wetterzen-
las imgenes descargadas para que que- posicin (300,0). Podemos ver el resulta- trale.de/pics/D2u.jpg
pan en el mural. do usando el mtodo show().
la primera parte como clave en un dic-

W 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 to de hacer uso de


existe la clave, usaremos una lista como ese mtodo generar
valor con los distintos valores como una Excepcin. Por
entrada. Pero por qu vamos a realizar tanto, esos mtodos
nosotros el trabajo duro cuando alguien no pueden ser invo-
ya lo ha resuelto? cados desde fuera
Python trae en su distribucin estn- del objeto Collage.
dar una librera que nos ser de enorme De esta manera
utilidad. Alguien consider oportuno ela- Collage slo tiene un
borar un analizador de archivos de mtodo accesible
configuracin, se llama ConfigParser. desde el exterior,
Con ella podemos extraer la informacin genera Collage(). Se
del archivo de configuracin. ha separado la gene-
El archivo de configuracin se compo- racin de HTML de
ne de Secciones y Opciones. Cada la del collage para
seccin contiene varias opciones, y los posibilitar las futu-
nombres de las secciones y opciones ras extensiones del Figura 5: Nuestro panel de control espacial terminado y colocado en
deben ser nicos. Por eso las URLs objeto. Por ejemplo, una pgina web generada dinmicamente.
comienzan con url1, url2 y url3. podramos no que-
Pero esto no ser un problema, vemos rer generar un fichero HTML sino incor- Como siempre, se espera que el lector
cmo funciona ConfigParser (ver Listado porar la imagen en un programa. En tal dedique algo de tiempo a jugar con el
3). Como podemos apreciar en el ejem- caso heredaramos de Collage y creara- programa para adaptarlo a sus necesida-
plo, el uso de ConfigParser es muy senci- mos un nuevo mtodo generaCollage() des o ideas.
llo. Primero se crea el analizador, guar- que slo generase la imagen y la devol-
dndolo en la variable config. Despus viese. Conclusin
cargamos con el mtodo readfp() el El mtodo __generaHTML() genera el La complejidad de un programa Python
fichero de configuracin; este mtodo cdigo HTML de la pgina web. Un no depende de la cantidad de lneas de
tambin analiza el fichero. A partir de punto a resaltar es que genera un mapa cdigo que contenga, sino ms bien del
ese momento podemos realizar pregun- sobre el collage, de manera que sea nivel al que trabaje. En el programa de
tas al objeto almacenado en config. Con posible pulsar sobre las distintas imge- este artculo hemos hecho uso intensivo
sections() obtenemos una lista de las nes que en l aparecen. Al hacerlo se de libreras que han realizado acciones
secciones y con options() de las opcio- cargar la imagen a tamao natural. El muy complicadas por nosotros. Python
nes. Con esa informacin ya podemos mapa se genera recorriendo el dicciona- posee una amplio abanico de libreras a
recoger los datos necesarios usando el rio de imgenes. Cada entrada del dic- explotar, muchas de ellas con aos de
mtodo get(), al que pasamos una sec- cionario contiene un objeto de la clase desarrollo esperando a programadores con
cin y una opcin. Imagen. ideas originales que poner en prctica.
Imagen alberga la informacin de cada
Ensamblemos las Partes imagen descargada mientras el programa Recursos
Ahora ya tenemos: la almacena. Se almacenan los datos pro- [1] Grficos que usaremos:
Un sistema de configuracin, usando pios de cada imagen, como por ejemplo http://www-mgcm.arc.nasa.gov/Mars
ConfigParser. las coordenadas que ocupar finalmente Today/marstoday.gif
Un sistema para descargar las imge- en el collage. http://www.sec.noaa.gov/sxi/
nes, usando httplib. current_sxi_ 4MKcorona.png
Un sistema para manipular las imge- Listado 3: Uso de ConfigParser http://www.ssec.wisc.edu/data/sst/
nes, usando PIL. latest_sst.gif
01 >>> config =
Nos toca ahora unirlo todo para que http://www.wetterzentrale.de/pics/
ConfigParser.ConfigParser() D2u.jpg
genere la pgina que aparece en la 02 >>> config.readfp(open('colla-
Figura 5. El resultado final se puede des- [2] Python Imaging Library:
ge.conf'))
cargar de [5]. http://www.pythonware.com/
03 >>> config.sections() products/pil/
Crearemos una clase Collage con los 04 ['tamao', 'imgenes']
mtodos [3] The Gimp: http://www.gimp.org
05 >>>
__cargaConf() [4] Noticia sobre llamarada solar en
06 >>> config.options('imagen')
__descarga() Slashdot:
07 ['url1', 'url3', 'url2']
__totalXY() http://science.slashdot.org/science/05/
08 >>>
generaCollage() 09/08/1933205.shtml?tid=215&tid=14
09 >>>
__generaImagen() [5] Listado del programa final de este
config.get('imagen','url1')
__generaHTML() artculo: http://www.linux-
10 '"http://www-mgcm.arc.nasa. magazine.es/Magazine/Downloads/Es
Cuando un mtodo comienza con __ gov/MarsToday/marstoday.gif"' peciales/06_Python
se convierte en privado. Cualquier inten-

72 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 llos que se repitan pueden ser copiados y en la oficina en la que trabajaba. Haba
maana a la oficina. El jefe se acerca y te pegados) y cuando estuviese lista, hacer que rellenar un formulario web para dar
pide que vuelvas a pasar, otra vez!, un algo (magia vud?) y que se cargasen parte de las ventas. Esa tarea, debido a
montn de informacin a otra empresa a solos en la dichosa web. Y por supuesto, su volumen, requera que una persona
travs de la peor interfaz jams dise- sin que se enterase el jefe, as tendras perdiese toda una maana simplemente
ada: una web. ms tiempo para leer artculos como ste porque a nadie en la otra empresa se le
Carga la pgina, introduce tus datos de ;). ocurri la idea de hacer el proceso ms
acceso, pincha aqu, pincha all. Cuando Pues s, existe una manera de hacer rpido.
ests en la pgina con el formulario en exactamente lo que acabas de leer! Y As que ni corto ni perezoso mi jefe
cuestin debes introducir los datos y pin- vamos a explicarlo en este captulo. As cre un script en Perl que haca este tra-
char en un enlace o botn para enviar- podrs decirle a tu jefe que esta revista bajo a partir de un fichero de texto CSV.
los. Una y otra vez, una y otra vez. Quiz har a la empresa mucho ms produc- El problema es que lo hizo en Perl, y esta
durante horas. tiva. seccin va sobre Python. Hemos de
Acaso no hay una mejor manera de mudarnos todos a Perl para poder disfru-
hacer esto? Lo ideal sera poder usar tu Mechanize tar de este tipo de ventajas? No, gracias a
hoja de clculo preferida, rellenar los No es la primera vez que hago esto. Hace que John J. Lee decidi portar la librera
campos en ella de forma rpida (aque- algunos aos tuve este mismo problema Mechanize que Andy Lester cre en Perl

W W W. L I N U X - M A G A Z I N E . E S PYTHON 73
LIBRERAS Mechanize

(ver Recurso [1]) a Python (ver Recurso y a pedirle que busque la palabra usuario. La librera mechanize nos da lo
[2]). python para, a continuacin, conse- primero sin lo segundo. Nuestra inter-
Las distribuciones de Linux suelen per- guir una lista de las urls de los primeros faz de usuario sern llamadas a mto-
mitir instalarla como paquete. El pro- artculos que contengan esa palabra dos del objeto Browser.
blema es que la versin ms completa no (sern enlaces a ficheros PDF), ver Un problema que nos podemos encon-
est an liberada, y es recomendable Figura 1. De esta forma comprobaremos trar, ya desde el principio, es que nuestra
emplear la que se encuentra en el servidor cmo se trabaja con mechanize, ya que red disponga de un proxy para acceder a
de Subversion de John. El proceso es sim- tiene una forma peculiar de tratar el Internet. Este caso es bastante comn,
ple, instalamos el cliente de Subversion cdigo HTML. as que debemos indicrselo al objeto
que ms nos guste, aqu usar el estndar, Para ello debemos comenzar por almacenado en br:
y descargamos el cdigo fuente con: arrancar python e importar la librera
mechanize y re (expresiones regulares): >>> br.set_proxies(U
> svn co http://U {http : 192.168.1.254:8000,U
codespeak.net/svn/wwwsearch/U >>> import re ftp : 192.168.1.254:8000})
mechanize/trunk mechanize >>> import mechanize
Aqu he especificado un proxy para http
John emplea la librera setuptools de No ocurre nada particularmente vistoso, y otro para ftp, que suele ser lo normal.
Python para compilar e instalar la libre- a no ser que no hallamos instalado Ya tenemos nuestro navegador listo. Slo
ra, as que deberamos proceder a insta- correctamente la librera. Muy bien, tenemos que abrir una pgina:
larla antes de continuar. Despus slo seguidamente necesitamos crear un
debemos ejecutar: navegador: >>>> respuesta= br.openU
(http://www.linuxmagazine.es/)
> python setup.py build >>> br = mechanize.Browser()
> sudo python setup.py install Una vez abierta se nos devuelve un
Ahora ya lo tenemos en la variable br. objeto de respuesta. Este objeto contiene
Y listo. Ya tenemos nuestra librera No me refiero a un navegador grfico, todos los mtodos necesarios para poder
mechanize lista para trabajar. sino a todo lo que un navegador puede trabajar con la informacin devuelta por
hacer pero sin la parte grfica. Me el servidor web. Por ejemplo, podramos
Juguemos con una Web explico, un navegador posee un motor imprimir el contenido HTML de la
Comencemos con algo simple. Vamos a que interacta con los servidores web y pgina:
conectar con la web de Linux Magazine una interfaz grfica que interacta con el
>>>> printU
Listado 1: Nuestra Araa web respuesta.read()
<html>....
01 import re 20
02 import mechanize 21 #eliminamos duplicados
No pongo aqu la informacin devuelta
03 22
porque podra ocupar una pgina com-
04 br = mechanize.Browser() 23 urls =
pleta. Adems es de poca utilidad. Lo
05 dict(zip(urls,urls)).keys()
interesante de mechanize es que genera
06 br.set_handle_robots(False) 24
la pgina y nos permite acceder a las par-
07 25
tes jugosas de la misma de forma muy
08 respuesta = 26 r =
sencilla. Digamos que queremos saber
br.open(http://www.linux- re.compile(.*/(\d+)/(.*)$)
qu formularios contiene la pgina:
magazine.es/) 27
09 28
>>> for form in br.forms():
10 br.select_form(nr=1) 29 for url in urls:
... print form
11 30
...
12 br[words]=python 31 m= r.match(url)
<POST http://U
13 32 nombre =
www.linux-magazine.es/Readers/U
14 br.submit() m.group(1)+-+m.group(2)
Newsletter/reply application/U
15 33 print nombre
x-www-form-urlencoded
16 # Estamos en la pgina de 34
<HiddenControlU
resultados 35 respuesta = br.open(url)
(subject=subscribe)U
17 36 datos = respuesta.read()
(readonly)>
18 #primer resultado 37
<TextControl(email=Tu email)>
19 urls = [url.absolute_url for 38 fichero = open (nombre,w)
<SubmitControl(<None>=OK)U
url in 39 fichero.write(datos)
(readonly)>>
br.links(url_regex=re.com- 40 fichero.close()
<POST http://U
pile(rpdf$))]
www.linux-magazine.es/U

74 PYTHON 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 >>> br.select_formU do hasta aqu: queremos los enlaces con
x-www-form-urlencoded (name = miformulario) la palabra python. Ya tenemos nuestro
<TextControl(words=)>> formulario relleno, ahora debemos pul-
Pero como no es este nuestro caso, lo sar el botn. Pero cmo? Pues con el
Mechanize tiene su propio lenguaje para seleccionaremos por posicin: mtodo submit:
representar partes de la pgina, y aqu
podemos ver un ejemplo del mismo. >>> br.select_form(nr = 1) >>> respuesta = br.sumbit()
Nos dice que hay dos formularios, que
emplean POST como mtodo de comuni- Empleamos el nmero 1, porque los for- Lo que acabamos de hacer es ms com-
cacin con la pgina. Muy bien, noso- mularios estn numerados comenzando plejo de lo que parece, qu ha ocurrido?
tros queremos acceder al segundo en 0. Con este mtodo ya tenemos selec- Al ejecutar submit hemos enviado los
puesto, que es el que emplea la url cionado el formulario, pero ste se com- datos del formulario al servidor, que nos
http://www.linuxmagazine.es/s earch. pone a su vez de varios elementos habr respondido redirigindonos a la
Este formulario tiene un campo de texto incrustados. Cmo podemos seleccio- pgina con los resultados. El contenido
llamado words. Al principio cuesta un narlos? Por suerte para nosotros, John, el de esta interaccin se almacena en res-
poco entender este lenguaje, pero con desarrollador, ha empleado toda la puesta, que no es ni ms ni menos que
un poco de prctica no es tan compli- potencia de Python y ha realizado un otro objeto que envuelve un documento
cado. truco de magia: hacer que el objeto HTML. Debemos recoger todos los enla-
De acuerdo, tenemos que acceder al almacenado en br se comporte como un ces que nos interesan.
segundo formulario, as que le indicamos tipo diccionario Python. Si el elemento Y aqu viene otro punto fuerte de
a br, nuestro navegador virtual, que input donde hay que escribir las palabras mechanize: su integracin con las expre-
emplee este formulario. Es posible realizar a buscar se llama words, entonces siones regulares. Desde luego que John
la seleccin por posicin o por nombre. El todo lo que tenemos que hacer es: no nos iba a fallar en este aspecto. Pode-
nombre vendra indicado por el parmetro mos elegir un enlace usando una expre-
HTML name, que el desarrollador de la >>> br[words]=python sin regular, de forma que no tenemos
web de Linux Magazine ha decidido igno- que ir buscando a tontas y a locas. Con
rar, al fin y al cabo no es obligatorio. As de simple! Es o no maravilloso este saber ms o menos qu formato tendr
Si se diese el caso de que el formulario mdulo? Esto s que es cdigo compacto el enlace que deseamos, podremos con-
tuviese un nombre, podramos seleccio- en estado puro. Despus de la euforia seguir la informacin que contiene. Pero
narlo con: debemos volver al asunto que nos ha tra- antes veamos qu deberamos hacer si

W W W. L I N U X - M A G A Z I N E . E S PYTHON 75
LIBRERAS Mechanize

no supiramos muy bien qu buscamos. expresin regular), que no hace otra en cuenta que en un diccionario no pue-
Al igual que con los formularios, los cosa que localizar cadenas acabadas en den existir dos llaves iguales, el resul-
enlaces se pueden recorrer como si fue- la letras pdf, y para ello podemos el tado es que al extraer las llaves obtene-
sen una lista: smbolo $ al final de pdf. Como mos la misma lista pero eliminando los
decamos, re.compile() devuelve un duplicados.
>>> for link in br.links(): objeto que reconoce la expresin regu- Complicado? S, como las lneas ante-
... print link lar, lo que podramos llamar una expre- riores, pero indudablemente til y que
... sin regular compilada, y lo almacena- requiere muy pocas pulsaciones del
Link(base_url=http://U mos en el argumento con nombre teclado.
www.linux-magazine.es/,U url_regex. La funcin br.links() lo reco- De acuerdo, ya tenemos los enlaces
url=/,text=logoOL.gif[IMG],U noce y sabe que debe buscar enlaces Y qu hacemos con ellos ahora? Pues
tag=a, attrs=[(href, /)]) que al reconocerlos con la expresin podramos descargarlos: recorriendo la
Link(base_url=http://U regular devuelvan True. lista y usando la funcin br.open() para
www.linux-magazine.es/,U br.links() generar as una lista de cargarlos, y la respuesta.read() para leer
url=/,text=logoOR.gif[IMG],U enlaces, y aqu entra en funcin la clu- el contenido del fichero, guardndolos
tag=a, attrs=[(href, /)]) sula for ... in ..., que no hace otra cosa en un fichero con nombre igual a la
.... que recorrer la lista y devolver los enla- ltima parte de la URL. Por desgracia,
ces bajo el nombre de variable url. La muchos de ellos se llaman
Aqu slo se muestran los dos primeros. lista de compresin se compone de cada Python.pdf, as que vamos a usar el
Si pruebas esto mismo en tu equipo uno de esos enlaces, con nombre url, de nmero de esa revista en el nombre.
vers que hay un nmero respetable de lo que nos quedamos con su atributo Puedes ver el cdigo completo en el Lis-
enlaces en esta pgina en concreto. De absolute_url: su ruta completa. Y con tado 1.
nuevo mechanize nos muestra lo que esto acabamos. Todo se reduce a una Pero, con este cdigo slo podemos
entiende por un enlace. Pero como noso- sola lnea de Python, escribimos poco conseguir los primeros resultados no?
tros sabemos lo que queremos, podemos pero vale por decenas de lneas! S, para mejorarlo y que descargue todos
pasar directamente a la accin con las An tenemos un problema, la web de los resultados slo tendramos que loca-
expresiones regulares: resultado de bsqueda de Linux Maga- lizar el enlace a siguiente resultado y
zine devuelve los resultados duplicados. pulsar en l con el mtodo
>>> urls =U Aplicando un poco de Kung Fu Python br.follow_link(). Dejo al lector que
[url.absolute_url for url inU podemos deshacernos de ellos en una piense cmo hacerlo, dando una sola
br.links(url_regex=re.compileU lnea: pista: emplea un bucle hasta que no
(rpdf$))] encuentres links que se correspondan
>>> urls = dict(U con la expresin regular.
Python comprime en poco cdigo mucho zip(urls,urls)).keys()
trabajo, as que esta nica lnea requiere Conclusin
una explicacin. Comencemos por la Zip significa cremallera en ingls, y eso No es de extraar que Google utilice
lista de compresin. En Python es posi- es precisamente lo que hace la funcin Python. De hecho, el propio Guido Van
ble crear listas a partir de definiciones de zip(). Cierra dos listas como si fuese una Rossum cre una araa web con una de
lo que se supone que va en las mismas. cremallera: las primeras implementaciones de
En este caso hay que comenzar por el Python ya hace algunos aitos. Hemos
cdigo: >>> zip([uno,dos,U podido comprobar cmo podemos usar
tres],[1,2,3]) una pgina web como si estuvisemos
br.links(url_regex=U [(uno,1),(dos,2),U delante de un navegador mediante la
re.compile(rpdf$)) (tres,3)] magnfica, y an en estado Beta, librera
mechanize, y empleando un nmero de
Este cdigo localiza todos aquellos enla- Esto puede resultar muy conveniente, lneas de cdigo realmente minsculo.
ces que se puedan identificar con el porque precisamente una lista con tuplas La prxima vez que el lector se enfrente
argumento que pasemos al mtodo de 2 valores es lo que necesitamos para a un trabajo tedioso con una pgina web,
br.links(). Es posible usar un nmero, crear un diccionario. puede que tenga un par de ideas para
como hicimos con el formulario ante- hacer que el ordenador trabaje por l
riormente, pero en lugar de eso vamos a >>> dict(zip([uno,dos,U gracias a cierta serpiente.
emplear una expresin regular. As que tres],[1,2,3]))
usamos la funcin re.compile(), que no {dos: 2, tres: 3,U Recursos
hace otra cosa que generar un objeto uno: 1}
[1] Mechanize para Perl: http://search.
que contiene un reconocedor de la cpan.org/dist/WWW-Mechanize/
expresin regular que pasamos como Y del diccionario podemos obtener las
[2] Mechanize para Python: http://
parmetro. En nuestro caso es pdf$, llaves usando el mtodo keys(). Si hace-
wwwsearch.sourceforge.net/
(la r de delante le indica a la funcin mos todo esto con un lista, cerrndola mechanize/
que la cadena se corresponde con una con ella misma en cremallera, y teniendo

76 PYTHON 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 programa-

cin Python. Por Ana M. Ferreiro y Jos A. Garca

La biblioteca ReportLab crea directa- que hay que seguir para instalar y confi- mar ejemplo1.py, las siguientes lneas
mente documentos PDF basndose en gurar ReportLab. de cdigo:
comandos grficos y sin pasos interme- El paquete pdfgen es el nivel ms bajo
dios, generando informes en un tiempo para generar documentos PDF, que se from reportlab.pdfgenU
extremadamente rpido y basa esencialmente en import canvas
siendo de gran utilidad en una secuencia de instruc- c=canvas.Canvas("primer.pdf")
los siguientes contextos: ciones para dibujar cada c.drawString(50,500, " MiU
generacin dinmica de pgina del documento. El PRIMER PDF")
PDFs en aplicaciones web objeto que proporciona las c.drawString(250,300,U
(empleado con Zope), operaciones de dibujo es "Coordenada=(250,300) ")
generacin de informes y el Canvas. El Canvas mide c.drawString(350,200,U
publicacin de datos igual que una hoja de "(350, 10)")
almacenados en bases de papel blanco, con puntos c.drawString(150,400,U
datos, embebiendo el sobre la misma identifica- "Aprendiendo REPORTLAB")
motor de impresin en dos mediante coordenadas c.showPage()
aplicaciones para conse- cartesianas (X,Y), que por c.save()
guir la generacin de defecto tienen el origen
informes a medida, etc. Figura 1: Coordenadas carte- (0,0) en la esquina infe- Probamos el programa y vemos que en
sianas de una hoja. rior izquierda de la pgi- el mismo directorio ya se ha creado un
Primeros Pasos na. La coordenada X va fichero llamado primer.pdf, anlogo al
Lo primero es tener instalados Python y hacia la derecha y la coordenada Y que se muestra en la Figura 2, sin nece-
ReportLab para realizar todas las prue- avanza hacia arriba (ver Figura 1). sidad de realizar ningn otro paso inter-
bas que van surgiendo y las que se nos Para crear nuestro primer PDF basta medio. Mediante la lnea from repor-
ocurran. En [1] se detallan los pasos escribir en un fichero, que podemos lla- tlab.pdfgen import canvas importamos

W W W. L I N U X - M A G A Z I N E . E S PYTHON 77
LIBRERAS ReportLab

Canvas, utilizado para dibujar en el Muchas veces querremos


PDF. El comando canvas.Canvas adaptar el dibujo a las
(path__fichero) permite indicar el nom- dimensiones de la hoja, por
bre con el que se guardar el PDF. El lo que necesitamos conocer
mtodo draw String(x,y,cadena_texto) el ancho y el alto de la
empieza a escribir el texto en la coorde- misma: ancho= tipo
nada (x,y) (se puede probar a cambiar _hoja[0] y alto=tipo_hoja
las diferentes coordenadas). El mtodo [1]; donde tipo_ hoja puede
showPage() crea la pgina actual del ser letter, A4, A5 , etc.
documento. Finalmente, save() guarda La clase Canvas dispone
el fichero en el path indicado. de diferentes herramientas
En el ejemplo previo hemos creado para dibujar lneas, circun-
un PDF sin especificar el tamao del ferencias, rectngulos,
documento, si queremos fijar el tamao arcos, etc. Adems permite Figura 2: Primer documento generado. El resultado impreso
de la hoja (A4, letter, A5, etc.) bastara modificar el color de los refleja cmo controlar las coordenadas de una hoja.
indicarlo en el Canvas mediante: objetos, rotar, trasladar,
indicar tipo y tamao de fuente, etc. En ReportLab encontraremos muchas
from reportlab.lib.U el cdigo del Listado 1 podemos ver otras).
pagesizes import letter,A4,A5,U cmo se dibujan, por ejemplo lneas,
A3 mediante canvas.line (x1,y1,x2 ,y2); Aadiendo Imgenes
c=canvas.Canvas("primer.pdf",U crculos, empleando el mtodo En este instante ya podemos demostrar
pagesize=letter) canvas.circle (x_centro,y_ centro, radio, nuestra creatividad en dibujo artsti-
stroke=1, fill=1); y rectngulos con co, aunque de un modo bastante labo-
El tamao de las hojas se importa esquinas redondeadas, con canvas. rioso. Seguro que ms de uno preferi-
mediante from reportlab. lib.pagesizes round Rect (x,y, ancho, alto ,angulo, mos incluir en nuestros ficheros imge-
import letter, A4,A5,A, y se especifica stroke =1,fill=0). Ntese que cada vez nes ya creadas. Pues esto es posible: en
en el Canvas con la propiedad pagesize. que se quiera emplear un color nuevo el rea de descarga de Linux Magazine
hay que indicarlo mediante canvas tenemos la imagen Tux2.png para las
Listado 1: ejemplo2.py setFillColor RGB(r,g,b), para el color de diferentes pruebas.
relleno, o canvas. setStroke ColorRGB A la hora de incluir imgenes pode-
01 from reportlab.pdfgen import
(r,g,b), para fijar el color de las lneas. mos optar por las siguientes opciones.
canvas
La eleccin del tipo de fuente se realiza La primera y ms sencilla, pero con la
02 c=canvas.Canvas
usando canvas .SetFont (tipo_ fuente, que no nos es posible rotar, trasladar, ni
("canvas_draw.pdf")
tamao). En la Figura 3 se muestra el redimensionar es mediante el mtodo
03 c.setFont("Helvetica",24)
PDF que creamos con el simple cdigo drawImage (image,x,y,width =None,
04 c.line(50,50,50,350)
del Listado 1. Podemos probar a cam- height=None) de la clase Canvas (si no
05 c.line(50,50,350,50)
biar propiedades (en el manual de se especifica el alto y el ancho, coloca la
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,stro-
ke=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 c.setFillColorRGB(0,0,0.5)
18 c.drawString(150,375,"Elipse")
19 c.showPage()
20 c.save()
Figura 3: Objetos que se pueden dibujar con un Canvas.

78 PYTHON W W W. L I N U X - M A G A Z I N E . E S
ReportLab LIBRERAS

Figura 4: Colocando imgenes con Figura 5: Modo en que se rota, traslada y Figura 6: Ejemplo de rotacin, traslacin y
drawImage. escala un objeto Drawing. escalado de imgenes.

figura con sus dimensiones originales). se instancia la clase; texto contiene el ,style)
Mediante las siguientes lneas podemos texto del prrafo, en el que se eliminan 12 story.append(P2)
crear un fichero similar al de la Figura 4. los espacios en blanco innecesarios;
bulletText indica si el prrafo se escribe El paquete reportlab.lib.styles contiene
c.drawImage("Tux2.png",0,0) con un punto al inicio del mismo; la estilos predefinidos. Con getSample
c.drawImage("Tux2,png",200,300,U fuente y otras propiedades del prrafo y Style Sheet obtenemos un estilo ejem-
width=30,height=60) el punto se indican mediante el argumen- plo. Tenemos un estilo para la cabecera
to style. Veamos cmo aadir un prrafo: y otro para el texto normal. Mediante
Si lo que pretendemos es rotar imgenes h1.pageBreakBefore=0 decimos que no
o escalarlas, debemos emplear los obje- 01 from reportlab.lib.styles queremos un salto de pgina cada vez
tos Image(x,y,ancho,alto,path_imagen) import getSampleStyleSheet que se escriba una cabecera h1, en caso
y Drawing(ancho,alto) que se importan 02 styleSheet=getSampleStyle contrario basta escribir 1. Los diferentes
mediante from reportlab.graphics.shapes Sheet() prrafos se van almacenando en la lista
import Image, Drawing. El objeto Dra- 03 story=[] story porque posteriormente se aaden
wing puede escalarse, rotarse y trasla- 04 h1=styleSheet['Heading1'] al pdf a travs el paquete SimpleDoc
darse; pero hay que tener en cuenta que 05 h1.pageBreakBefore=0 Template de reportlab.platypus:
todas estas operaciones son acumulati- 06 h1.keepWithNext=1
vas (ver Figura 5). En el Listado 2 pode- 07 h1.backColor=colors.red doc=SimpleDocTemplate(U
mos ver cmo emplear correctamente 08 P1=Paragraph("Estilo Cabecera "paragrahp.pdf", pagesize=A4,U
estos objetos (Figura 6). Obsrvese que - h1 ",h1) showBoundary=1)
ahora el PDF no se genera a partir de un 09 story.append(P) doc.build(story)
Canvas, sino que se genera mediante 10 style=styleSheet['BodyText']
renderPDF.drawToFile (d,"canvas_ 11 P2=Paragraph("Estilo En este caso se genera un PDF con tan-
image 2.pdf"), donde d=Drawing BodyText" tas pginas como sea necesario.
(A4[0] ,A4[1]). Podemos probar a modi-
ficar los valores de los distintos mtodos Listado 2: Jugando con Imagenes (ejemplo3_2.py)
scale, rotate, translate; observaremos
01 from reportlab.graphics.shapes 11 IMAGES.append(d)
que a veces la imagen puede desapare-
import Image, Drawing 12 d=Drawing(80,100)
cer del folio, eso es debido a que los
02 from reportlab.graphics import 13 d.add(img)
valores que se dan hacen que nos salga-
renderPDF 14 d.translate(10,0)
mos de las dimensiones de la pgina.
03 from reportlab.lib.pagesizes 15 d.scale(2,2)
import A4 16 d.rotate(-5)
Creacin de Prrafos y Tablas
04 inpath="Tux2.png" 17 IMAGES.append(d)
La clase reportlab.platypus.Paragraph
05 IMAGES=[] 18 d=Drawing(A4[0],A4[1])
permite escribir texto formateado (justifi-
06 d=Drawing(80,100) 19 for img in IMAGES:
cado, alineado a la derecha o izquierda,
07 img=Image(200,0,80,100,inpath) 20 d.add(img)
centrado) en un aspecto elegante, ade-
08 d.add(img) 21 renderPDF.drawToFile(d,"can-
ms de modificar el estilo y color de tro-
09 d.rotate(45) vas_image2.pdf")
zos de la lnea a travs de XML. Mediante
10 d.scale(1.5,1.5)
Paragraph(texto,style, bullet Text=None)

W W W. L I N U X - M A G A Z I N E . E S 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 ejem-
plo, si deseamos que el texto de la pri-
mera 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)])

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
Figura 7: Ejemplo de prrafo, tabla e imagen. objeto a la lista story.
Ahora ya sabemos todo lo necesario
Comprobar esto es tan sencillo como Una tabla se crea aadiendo una lista de para crear nuestros propios carteles,
tener un texto muy largo. Si aadimos listas, donde cada componente de la informes, catlogos, presentaciones,
un prrafo cuyo texto sea "Hola"*300, lista guarda la informacin de cada fila. etc.
seguro que se generan ms de una Si queremos construir una tabla de 5
hoja. filas y 3 columnas hacemos Recursos
Si lo que queremos es aadir una
[1] Reportlab:
tabla, es necesario importar from repor- t=Table([['','Ventas',U http://www.reportlab.org
tlab .platypus import Table, TablsStyle. 'Compras'],U

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


01 from reportlab.lib.pagesizes 16 P=Paragraph("Estilo Cabecera - 36 story.append(t)
import A4 h1 ",h1) 37
02 from reportlab.lib.styles 17 story.append(P) 38 story.append(Spacer(0,15))
import getSampleStyleSheet, 18 P=Paragraph("Estilo h2 ",h2) 39 P=Paragraph("Cabecera h1",h1)
ParagraphStyle 19 story.append(P) 40 story.append(P)
03 from reportlab.platypus import 20 style=styleSheet['BodyText'] 41
Spacer, 21 texto=" Texto escrito para ver 42 cadena='''Mediante ReportLab es
SimpleDocTemplate, Table, como crear ficheros PDF."+\ 43 posible generar ficheros PDF
TableStyle 22 "Este parrafo esta escrito 44 de gran calidad. Es posible
04 from reportlab.platypus import en estilo BodyText" 45 incluir graficos, imagenes,
Paragraph, Image 23 texto_largo=texto 46 tablas; creando informes
05 from reportlab.lib import 24 #texto_largo=texto*100 47 de gran calidad'''
colors 25 P=Paragraph(texto_largo,style) 48 P=Paragraph(cadena,style)
06 26 story.append(P) 49
07 styleSheet= 27 story.append(Spacer(0,12)) 50 story.append(Spacer(0,15))
getSampleStyleSheet() 28 51
08 story=[] 29 t=Table([['','Ventas', 52 img=Image ("Tux2.png",
09 h1=styleSheet['Heading1'] 'Compras'], width=80,height=100)
10 h1.pageBreakBefore=0 30 ['Enero',1000, 2000], 53 story.append(img)
11 h1.keepWithNext=1 31 ['Febrero',3000,100.5], 54 doc=SimpleDocTemplate("para-
12 h1.backColor=colors.red 32 ['Marzo',2000,1000], grahp.pdf",pagesize=A4,showBoun
13 h2=styleSheet['Heading2'] 33 ['Abril',1500,1500]] dary=1)
14 h2.pageBreakBefore=0 34 ) 55 doc.build(story)
15 h2.keepWithNext=1 35

80 PYTHON W W W. L I N U X - M A G A Z I N E . E S
SERVICIO Autores / Contacto

Autores Datos de Contacto


Ana Mara Ferreiro 65, 75 Director
Jose Antonio Garca 65, 76 Paul C. Brown
Jos Mara Ruz 6, 15, 10, 19, 23, 28, 33, 39, 43, 48, 55, 59, 69, 73 Coolaboradores
Frank Wiles 52 Paul C. Brown, Jos Mara Ruz

Paul C. Brown 3, 10 Traductores


Paqui Martn Vergara, Lucas Gonzlez, Vctor
Pedro Orantes 28, 33, 59, 69 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 escrupu-
losamente 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 con-
tenidos 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

Das könnte Ihnen auch gefallen