Python power 1
-
Upload
manuel-velasco -
Category
Technology
-
view
43 -
download
0
Transcript of Python power 1
W W W . L I N U X - M A G A Z I N E . E S
8413042594529
00006
BÁSICOIntroducción a
Python con
ejemplos prácticos
Denis Babenko - 123RF.com
MANUAL PRÁCTICO
DVD GRATIS
Debian 6
17PROYECTOS COMPLETOS
Y FUNCIONALES
Tu Guía PrácticaAprende de desarrolladoresprofesionales y de sus ejemplos del mundo real
■ Automatiza formularios web
■ Integra Python con Java, .Net y Qt
■ Amplía Open/LibreOffice
■ Crea y manipula PDFs
■ Maneja grandes volúmenes de datos
ENERO 2012 Península y Baleares 6,95 €Canarias 7,05 €
Especial
06
INFRAESTRUCTURASExplota los motores tras
Facebook, Google y Twitter
48
MÓDULOSDescubre cómo programar
para 3D y a manipular datos
e imágenes
59
En la tienda de Linux Magazine (www.linux-magazine.es/tienda) vendemos revistas y libros que pue-
den ser de interés a nuestros lectores. Recuerda también que con una subscripción Digital o Club,
podrás 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 certificación com-
pleta LPIC-1 (exámenes 101 y 102). Más de 250 páginas
de ejemplos reales tomados de ambos exámenes expli-
cados en todo detalle con ejercicios para prácticas 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 garantía de Linux Magazine.
■ ”La guía perfecta en castellano para preparar el exa-
men para cualquier persona que tenga conocimien-
tos de Linux.”
■ ”Se ciñe muy bien a los objetivos del nivel 1 de LPI
(LPIC-1) actualizados en Abril de este año, cosa que
es de agradecer.”
■ ”Un avance muy importante en el desarrollo de los
programas de certificación LPI en España.”
www.lpi.org.es
Consíguelo en nuestra tienda.
DVD/EDITORIALDebian 6
3PYTHONWWW. L I NUX - MAGAZ INE . ES
Especial Python
En el DVD de este especial encontrarás
la versión Live de la última iteración
estable de Debian [2] para arquitecturas
de 32 bits (la más universal). Al ser
Debian la más completa de las distros
GNU/ Linux, podrás encontrar en sus
repositorios todas las herramientas,
módulos e infraestructuras que necesita-
rás para seguir los artículos de este
especial.
DVD: Debian 6.0.3
Aunque la norma en el sangrado de
código en Python es que las tabulaciones
a principio de línea sean múltiplos de cua-
tro espacios, para favorecer la legibilidad
de los listados en este especial, cada tabu-
lación se ha reducido a un solo espacio.
De esta manera, código que normal-
mente se escribiría como:
for i in range (1, 5):
if ((i % 2) == 0):
print i,” es par”
else:
print i,” es impar”
Se reproduce así en los artículos:
for i in range (1, 5):
if ((i % 2) == 0):
print i,” es par”
else:
print i,” es impar”
A pesar de ser técnicamente correcto,
animamos a que, si se transcribe código
para su ejecución, se utilice la conven-
ción de los 4 espacios.
En todo caso, todo el código está dispo-
nible, con su formato convencional, en el
sitio de Linux Magazine en [1].
Formato del Código en este Especial
[1] Todo el código de este especial: http://
www. linux-magazine. es/ Magazine/
Downloads/ Especiales/ 06_Python
[2] Debian: http:// www. debian. org/ index.
es. html
Recursos
Bautizado en honor al grupo de cómicos británicos Monty
Python, Guido Von Rossum concibió este lenguaje en los
años 80, comenzando su implementación en 1989. Gra-
cias a su sencillez y a la idea de que el código bello se
consigue siendo explícito y simple, Python se ha
convertido en sus poco más de 20 años de exis-
tencia en el lenguaje favorito para scripts, aun-
que también para grandes infraestructuras y
aplicaciones.
Así, la mayor parte de los servicios de Goo-
gle y Facebook se construyeron utilizando
Python, las aplicaciones de diseño Inkscape
o Blender (entre muchas otras) utilizan un
motor Python para sus plugins. También se
utiliza ampliamente en la investigación en
entidades que van desde CERN hasta la
NASA.
Además, 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 enseñar programación del mundo real a todos
los niveles.
Con este especial pretendemos que el lector pueda
descubrir Python desde el principio, creando para ello
artículos que abordan desde tutoriales introductorios, hasta
programación de alto nivel y para usos avanzados. Cada sección
viene con varios ejemplos prácticos, código y soluciones extraídas del
mundo real. ■
CONTENIDO Python 01
4 PYTHON WWW. L I NUX - MAGAZ INE . ES
06 Primeros PasosPython es un lenguaje potente,
seguro, flexible… pero sobre todo sen-
cillo y rápido de aprender, que nos
permite crear todo lo que necesitamos
en nuestras aplicaciones de forma ágil
y eficaz
10 Álbum FotográficoSiguiendo con nuestro paseo por
Python, vemos características básicas,
y concretamente en este artículo, el
tratamiento de ficheros creando un
programa para la ordenación de colec-
ciones de fotos.
15 Desparasitando SerpientesDa igual lo buenos programadores
que seamos, tarde o temprano dare-
mos con ese BUG que será nuestro
peor enemigo. Veamos cómo pode-
mos emplear herramientas para derro-
tarlo con mayor facilidad.
19 Sin NombrePython es un lenguaje de programa-
ción multiparadigma, y las funciones
lambda son parte fundamental de él,
aunque como veremos, existen bue-
nas razones para no abusar de ellas.
23 Python no hay más 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 tecnología
UNO de OpenOffice.
28 Cuando los Mundos ChocanOs descubrimos Jython, la forma mas
sencilla de desarrollar vuestras aplica-
ciones Java como si las programárais
con Python.
33 Limpieza TotalAJAX es la palabra de moda, Google
usa AJAX, Yahoo usa AJAX… todo el
mundo quiere usar AJAX pero ¿lo
usas tú? y más importante aún ¿qué
demonios es AJAX?
39 De Serpientes y Primates.NET está avanzando, y Python no se
ha quedado atrás. En lugar de comba-
tirlo, ha entrado en simbiosis con ella.
Con Ironpython podremos hacer uso
de toda la potencia de .NET desde
nuestro lenguaje favorito.
43 ¡Desarrollo Rápido!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 récord.
La desesperación 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».
03 DVD Debian 6
82 Información de Contacto
Otras Secciones
Introducción Avanzado
Integración
Integración
CONTENIDOPython 01
5PYTHONWWW. L I NUX - MAGAZ INE . ES
48 PyramidUno de los rivales de peso de Django
está creciendo en popularidad poco a
poco.
52 GuitarrazosLos creadores del proyecto Django nos
hablan de la formación de la Django
Software Foundation y mostramos
cómo comenzar con esta infraestruc-
tura web.
55 SerialesHacer que distintos servicios se comu-
niquen entre ellos es todo un pro-
blema que Facebook ha tratado de
solucionar con Thrift.
59 Cuaderno de Bitácora¿Te acuerdas de cuando cambiaste la
versión 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 bitácora.
65 Gráficas 3DCrear gráficos 3D no es nada difícil en
Python... sobre todo si tenemos a
mano la librería VTK.
69 Vigilantes del planeta¿Quién no ha querido alguna vez sen-
tirse como esos informáticos de la
NASA en su centro de control? Hoy
nos construiremos el nuestro y contro-
laremos el planeta y sus alrededores.
73 EnredadosPodemos automatizar comandos y
programas gráficos, ¿por qué no auto-
matizar la interacción con páginas
web? En este artículo crearemos un
pequeño script que puede ahorrarnos
mucho trabajo con el ratón.
77 ReportLabHoy en día se hace imprescindible dis-
poner de herramientas que permitan
generar informes en PDF de alta cali-
dad rápida y dinámicamente. Existen
diferentes herramientas para esta fina-
lidad, entre ellas cabe destacar Repor-
tLab, biblioteca gratuita que permite
crear documentos PDF empleando
como lenguaje de programación
Python.
Ver información en pág. 3
VERSIÓNDVDGNOME LIVE
Infraesctructuras
Librerías
Librerías
INTRODUCCIÓN Primeros Pasos
6 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Para empezar, debemos saber por qué
Python es interesante, por qué es tan
famoso y cada vez más utilizado. Mirando
un poco por Internet se pueden encontrar
multitud de aplicaciones que nos muestran
parte de las capacidades de este lenguaje
de alto nivel. Vamos a enumerar algunas
de sus sorprendentes características:
• Orientado a objetos – Esto no significa
que sea exclusivamente orientado a
objetos, podemos utilizarlo como quera-
mos, aunque le sacaremos más prove-
cho si usamos su implementación de
OOP (Programación Orientada a Obje-
tos).
• Libre y gratuito – Desde la red, pode-
mos descargar el interprete y su código
fuente, y al ser un lenguaje de script,
viene con la mayoría de las distros
GNU/ Linux de manera predeterminada,
siendo posible ver el código de una
enorme parte del software desarrollado
para Python.
• Portable – Al ser interpretado, podemos
ejecutar nuestros programas en cual-
quier S.O. y/ o arquitectura simplemente
teniendo instalado previamente el intér-
prete en nuestro ordenador .
• Potente – Realizar un programa bajo
este lenguaje seguramente nos costaría
entre la mitad o la cuarta parte del
tiempo que tardaríamos en desarrollar el
mismo programa en C/ C++ o Java.
• Claro – Puede que ésta sea una de las
características más alabadas de Python:
Los programas escritos en este lenguaje
tienden a ser fáciles de comprender y,
por tanto, de mantener por terceros. Una
enorme ventaja frente a lenguajes como
C o Perl.
Pero veamos una breve comparativa con
otros lenguajes:
Hola Mundo en C:
main ()
{
printf(“Hola Mundo”);
}
Hola Mundo en Java:
public static void U
main(String args[])
{
System.out.println(“Hola U
Mundo”);
}
Hola Mundo en Python:
print “Hola Mundo”
Aunque los “Hola Mundo” no son muy
indicativos de nada, nótese la ausencia de
puntos y comas, llaves, declaración de fun-
ciones y otros “trastos” que entorpecen el
código. Esto es incluso más obvio en pro-
gramas más largos.
Python dispone de otras características
que lo convierten en el lenguaje favorito
de una comunidad de desarrolladores
cada vez más amplia. Por ejemplo, per-
mite la declaración dinámica de varia-
bles, es decir, no tenemos que declarar
las variables ni tener en cuenta su
tamaño, ya que son completamente diná-
micas. Además, dispone de un gestor de
memoria que, de manera similar al de
java, se encargará de liberar memoria de
objetos no utilizados. Sin embargo, y al
igual que Java, no permite usar la memo-
ria a bajo nivel como C, con el que nos
podíamos referir a zonas de memoria
directamente.
Además se puede combinar con otros
múltiples lenguajes de programación.
Podemos mezclar en nuestras aplicaciones
Python y Java (Jython – ver el artículo al
respecto en la página 28 de este especial),
por ejemplo. O Python con C/ C++, lo
cual hace que resulte mas potente si cabe.
Python también cuenta con una amplia
biblioteca de módulos que, al estilo de las
bibliotecas en C, permiten un desarrollo
rápido y eficiente.
La sencillez de Python también ayuda a
que los programas escritos en este lenguaje
sean muy sintéticos. Como podemos ver
en el ejemplo “Hola Mundo” anterior, la
simplicidad llega a ser asombrosa. Si este
programa ya supone ahorrarte 4 ó 5 líneas
de código, con una sintaxis tan sencilla y
ordenada podemos imaginar que un pro-
grama de 1000 líneas en Java, en Python se
redujeran unas 250.
Python es un lenguaje potente, seguro, flexi-
ble… pero sobre todo sencillo y rápido de
aprender, que nos permite crear todo lo que
necesitamos en nuestras aplicaciones de
forma ágil y eficaz. Por José María Ruíz
Aprende a programar con este lenguaje de programación multiplataforma
Primeros Pasos
Anita
Patte
rson - m
org
uefile
.com
UsoPara empezar a matar el gusanillo, pode-
mos ir haciendo algunas pruebas intere-
santes. Vayamos al intérprete Python. Para
ello, basta con escribir ‘python’ en el
prompt de una terminal (por ejemplo,
Bash en GNU/ Linux o Powershell en Win-
dows) y probar nuestro “Hola Mundo”:
>>> print ‘Hola Mundo’
Hola Mundo
Ahora probemos a utilizar algunas varia-
bles:
>>> suma = 15 + 16
>>>
>>> print ‘el resultado de la U
suma es: ‘, suma
el resultado de la suma es: 31
Es recomendable trastear un poco con esto
antes de ponernos a programar algo más
complicado, ya que de esta manera es más
sencillo hacerse con la sintaxis mucho más
rápidamente viendo los resultados de cada
prueba.
Veamos ahora alguna propiedad intere-
sante de Python. Los ficheros en Python
no tienen por qué llevar extensión nin-
guna, pero seguramente querremos tener-
los diferenciados del resto de ficheros que
tengamos. Por ello se suele utilizar la
extensión .py.
Pero ¿cómo sabe el sistema que intér-
prete utilizar cuando queramos ejecutar
nuestros scripts? Sencillo: Imaginemos que
tenemos un ejemplo.py, al ser un lenguaje
tipo script, debemos poner #! seguido de la
ruta del intérprete de Python en la cabe-
cera del fichero. De esta manera, y dándole
permisos de ejecución (chmod +x ejem-
plo.py en GNU/ Linux), obtenemos un pro-
grama listo para su ejecución.
Si nuestro intérprete Python se halla en
/usr/ bin/ , el contenido de ./ ejemplo.py
quedaría, pues, como sigue:
#! /usr/bin/python
print ‘Hola Mundo’
Después de toda la introducción técnica va
siendo hora de que veamos cómo es el for-
mato de los programas en Python. Esto es
importante porque, mientras la norma en
la mayoría de los lenguajes es dejar al pro-
gramador la decisión de la manera en que
deben ser formateados los archivos fuente,
en Python es obligatorio hacerlo de cierta
forma. En Python todo se hace de un solo
modo, de hecho es parte de su filosofía:
“Solo Hay Una Manera de Hacer Las
Cosas”.
Comencemos con lo más simple en todo
lenguaje, la asignación a una variable:
cantidad = 166.386
(Para los que no lo recuerden, 166,386 era
la cantidad de pesetas que hay en un euro).
Lo primero que hay que apreciar es que
no se usa el ;. Esto es una característica de
las muchas que hacen a Python diferente.
Una sentencia acaba con el retorno de
carro, aunque el ; se puede usar cuando
dos sentencias están en la misma línea:
cant = 166.386; ptas = 3000
Como es un lenguaje dinámico, no hay
que declarar el tipo de las variables, pero
una vez que una variable ha sido definida
(lo que se hace asignándole un valor), esa
variable guarda su tipo y no lo cambiará a
lo largo de la ejecución del programa.
También tenemos a nuestra disposición
los operadores habituales de otros lengua-
jes: +, -, *, /, etc. Y una vez que sabemos
cómo manejar operadores y asignaciones,
se pueden hacer cosas útiles, pero sólo de
manera lineal. Para que la ejecución no sea
lineal necesitamos los bucles y los condi-
cionales.
El tema de los bucles en Python es algo
especial. Puede que estemos acostumbra-
dos a los que son de tipo C:
for (a = 1; a < 10; a++) U
printf(“%d\n”,a);
Sin embargo, en Python se toma un enfo-
que funcional prestado de otros lenguajes
como Lisp o Haskell. Se utiliza una fun-
ción especial llamada «range». La función
«range» genera una lista de números:
range(1,10)
generará una ristra de números del 1 al 9.
De esta manera, podemos utilizar una fun-
ción range para crear un bucle for en
Python de la siguiente manera:
for i in range(1,10)
Este bucle iteraría con i desde 1 a 9, ambos
inclusive. La versión del bucle while en
Python es más normal…
while(<condición>)
al igual que if…
if (<condición>)
¿Por qué no he puesto cuerpos de ejemplo
en esos bucles? Pues porque ahora viene
otra novedad. En Python no se usan las
famosas { y } para delimitirlas. Se decidió
(y no a todo el mundo le gusta) que se usa-
ría la posición como delimitador. Esto, así,
suena algo extraño, pero si se ve es mucho
más sencillo:
>>> cantidad = 2
>>> for i in range(1,10):
print cantidad*i
Comencemos mirando a ese :. Los dos
puntos marcan el inicio de un bloque de
código Python. Ese bloque aparecerá en
bucles, funciones o métodos. Si nos fija-
mos bien en la sangría de la siguiente
línea, vemos una serie de espacios (logra-
dos pulsando la tecla TABULADOR) y una
sentencia. Esos espacios son vitales, ya
que marcan la existencia de un bloque de
código. Además, son obligatorios.
Este es uno de los hechos más contro-
vertidos de Python, pero también mejora
mucho la legibilidad del código. Sólo existe
una manera de escribir Python, así que
todo el código Python se parece y es más
fácil de entender. El bloque acaba cuando
desaparecen esos espacios:
>>> for i in range(1,10):
... print cantidad*i
... cantidad = cantidad + 1
...
>>>
Funciones y ObjetosYa tenemos las piezas fundamentales para
entender las funciones y los objetos. La
declaración de una función en Python
tiene una sintaxis muy simple:
def nombre_funcion U
(<lista argumentos>):
<CUERPO>
Fácil ¿no? Al igual que las variables, a los
argumentos no se les asignan tipos. Exis-
ten muchas posibilidades en los argumen-
tos, pero los veremos más tarde. De
momento examinemos un ejemplo sim-
ple:
INTRODUCCIÓNPrimeros Pasos
7PYTHONW W W. L I N U X - M A G A Z I N E . E S W W W . L I N U X - M A G A Z I N E . E S
Estructuras de DatosUna de las razones por las que los progra-
mas scripts de Python resultan tan poten-
tes, es que nos permiten manejar estructu-
ras de datos muy versátiles de manera
muy sencilla. En Python estas estructuras
son las Listas y los Diccionarios (también
llamados Tablas Hash).
Las listas nos permiten almacenar una
cantidad ilimitada de elementos del mismo
tipo. Esto es algo inherente a casi todos los
programas, así que Python las incorpora
de fábrica. Las listas de Python también
vienen provistas de muchas más opciones
que sus semejantes en otros lenguajes. Por
ejemplo, vamos a definir una lista que
guarde una serie de palabras:
>>> a = [“Hola”, “Adios”, U
“Buenas Tardes”]
>>> a
[‘Hola’, ‘Adios’, U
‘Buenas Tardes’]
Python indexa comenzando desde 0, de
manera que ‘Hola’ es el elemento 0, ‘Adios’
el 1 y ‘Buenas Tardes’ el 2, y la longitud de
la lista es 3. Podemos comprobarlo de esta
forma:
>>> a[1]
‘Adios’
>>> len(a)
3
Es posible añadir elementos a las listas de
varias maneras. Si miramos el Listado 2,
veremos la más sencilla. Las listas también
se pueden comportar como una Pila, con
las operaciones append y pop. Con insert
introducimos un elemento en la posición
especificada (recuerda que siempre
comenzamos a contar desde 0). La facili-
dad con la que Python trata las listas nos
permite usarlas para multitud de tareas, lo
que simplificará mucho nuestro trabajo.
A pesar de su potencia, las listas no pue-
den hacerlo todo, existiendo otra estruc-
tura que rivaliza con ellas en utilidad, los
Diccionarios. Mientras las listas nos permi-
ten referenciar a un elemento usando un
número, los diccionarios nos permiten
hacerlo con cualquier otro tipo de dato.
Por ejemplo, con cadenas, de hecho, casi
siempre con cadenas, de ahí que su nom-
bre sea diccionario (véase el Listado 3).
Las listas y los diccionarios se pueden
mezclar: diccionarios de listas, listas de
diccionarios, diccionarios de listas de dic-
cionarios, etc. Ambas estructuras combi-
nadas poseen una enorme potencia.
Algoritmos + Estructuras de Datos= ProgramasAhora nos toca poner todo esto en prác-
tica. Lo normal es hacer un programa sen-
cillo. Pero en lugar de eso vamos a imple-
mentar algo que sea creativo. Este pro-
grama es el que se usa en el libro “La prác-
tica de la programación” de Pike y Kernig-
han para ilustrar cómo un buen diseño
sobrepasa al lenguaje que usemos para eje-
>>> def imprime (texto):
... print texto
>>> imprime(“Hola mundo”)
Hola mundo
>>>
Vuelve a ser sencillo. ¿Y los objetos? Pues
también son bastante simples de imple-
mentar. Podemos ver un ejemplo en el Lis-
tado 1. Con class declaramos el nombre de
la clase, y los def de su interior son los
métodos. El método __init__ es el cons-
tructor, donde se asignan los valores inicia-
les a las variables. __init__ es un método
estándar y predefinido, lo que quiere decir
que tendremos que usar ése y no otro para
inicializar el objeto. Todos los métodos,
aunque no acepten valores, poseen un
parámetro self. Este es otro punto contro-
vertido en Python; self es obligatorio, pero
no se usa al invocar el método. ¿Cómo se
crea el objeto?
>>> a = Objeto(20)
Es como llamar a una función. A partir de
este momento a es una instancia de
Objeto, y podemos utilizar sus métodos:
>>> print a.getCantidad()
20
>>> a.setCantidad(12)
>>> print a.getCantidad()
12
No hay que preocuparse por la administra-
ción de la memoria del objeto ya que,
cuando a no apunte al objeto, el gestor de
memoria liberará su memoria.
Ya tenemos las bases para construir algo
interesante. Por supuesto, nos dejamos
infinidad de cosas en el tintero, pero siem-
pre es mejor comenzar con un pequeño
conjunto de herramientas para empezar a
usarlas. En todo caso, tendremos tiempo
de profundizar en los siguientes artículos
de este especial.
INTRODUCCIÓN Primeros Pasos
8 PYTHON W W W. L I N U X - M A G A Z I N E . E S
01 class Objeto:
02 def __init__ (self, cantidad):
03 self.cantidad = cantidad
04
05 def getCantidad(self):
06 return self.cantidad
07
08 def setCantidad(self,cantidad):
09 self.cantidad = cantidad
Listado 1: Una Clase Sencilla01 >>> dic = {}
02 >>> dic[“Perro”] = “hace guauguau”
03 >>> dic[“Gato”] = “hace miaumiau”
04 >>> dic[“Pollito”] = “hace piopio”
05 >>> dic
06 {‘Perro’: ‘hace guau guau’,
07 ‘Gato’: ‘hace miau miau’,
08 ‘Pollito’: ‘hace pio pio’}
09 >>> dic[“Perro”]
10 ‘hace guau guau’
Listado 3: Ejemplo Diccionario
01 >>> b = [ 1 , 2 , 1 ]
02 >>> b.append(3)
03 >>> b.append(4)
04 >>> b
05 [1 , 2 , 1 , 3 , 4 ]
06 >>> b.remove(1)
07 >>> b
08 [2, 1, 3, 4]
09 >>> b.pop()
10 4
11 >>> b
12 [2, 1, 3]
13 >>> b.insert(1,57)
14 >>> b
15 [2, 57, 1, 3]
16 >>> b.append(1)
17 >>> b
18 [2, 57, 1, 3, 1]
19 >>> b.count(1)
20 2
21 >>> b.index(57)
22 1
23 >>> b.sort()
24 >>> b
25 [1, 1, 2, 3, 57]
26 >>> b.reverse()
27 >>> b
28 [57, 3, 2, 1, 1]
Listado 2: Adición y Eliminación de Elementos de Lista
cutarlo. En el libro se implementa el diseño
en C, C++, Java, Perl y AWK. Nosotros lo
haremos en Python (ver Listado 4).
El programa acepta un texto como
entrada y genera un texto como salida, pero
este segundo texto no tiene sentido. Lo que
queremos hacer es generar texto sin sentido
pero con estructuras que sí lo tengan. Puede
parecer algo muy complicado, pero no lo es
tanto si usamos la técnica de cadenas de
Markov. La idea es coger 2 palabras, elegir
una palabra que suceda a cualquiera de las
dos y reemplazar la primera por la segunda
y la segunda por la palabra escogida. De
esta manera vamos generando un texto que,
aunque carece de sentido, normalmente se
corresponde con la estructura de un texto
normal aunque disparatado.
Para hacer las pruebas es recomendable
conseguir un texto de gran tamaño. En tex-
tos pequeños no surtirá tanto efecto. En el
proyecto Gütenberg podemos conseguir
infinidad de textos clásicos de enorme
tamaño en ASCII. Pero somos conocedores
de que no todo el mundo entiende el
idioma anglosajón, así que en lugar de ir al
proyecto Gütenberg, podemos coger cual-
quier texto que queramos modificar, por
ejemplo, alguna noticia de política de un
diario digital o alguna parrafada de algún
blog.
Este programa es interesante porque per-
mitirá utilizar las estructuras de datos que
Python implementa, en particular en los
diccionarios, que generan una tabla donde
los índices serán cadenas de texto.
Veamos cómo funciona. Lo primero es ir
introduciendo en el diccionario dos prefi-
jos como índice y las palabras que les
siguen en el texto dentro de una lista refe-
renciada por ellos. Eso es un diccionario
con dos palabras como índice que contiene
una lista:
DICCIONARIO[ palabra 1, U
palabra 2] -> U
LISTA[palabra,...]
O sea, si tenemos las palabras “La gente
está … La gente opina”, crearemos un dic-
cionario de la siguiente forma:
>>> dict[‘La’, ‘gente’] = U
[‘está’]
>>> dict[‘La’, ‘gente’].U
append(‘opina’)
>>> dict[‘La’, ‘gente’]
[‘está’,’opina’]
Vamos haciendo esto de manera sucesiva
con todos los conjuntos de dos palabras, y
obtendremos un diccionario en el que
muchas entradas referenciarán a una lista
de más de un elemento.
La magia aparece cuando generamos el
texto, puesto que lo que hacemos es
comenzar por las dos primeras palabras, y
cuando existan varias posibilidades para
esa combinación (como con el ejemplo de
‘La’,’gente’), escogeremos aleatoriamente
entre ellas. Imaginemos que escogemos
‘opina’, entonces escribimos ‘opina’ por la
pantalla y buscamos en ‘gente’, ‘opina’ y
así sucesivamente, hasta llegar a no_pala-
bra.
Para entender mejor el funcionamiento
del programa recomendamos copiar el
código fuente y pasarle unos ejemplos
(pongamos, con cat texto.txt |
./markov.py) y ver los resultados. En el
Listado 4 vemos un ejemplo de la salida
utilizando el texto de este artículo. El texto
generado casi tiene sentido, pero no del
todo.
Después podemos intentar cambiar
cosas en el programa, por ejemplo, en
lugar de utilizar 2 palabras como índice del
diccionario, podemos probar con 1 o con
3, y también con el tamaño del texto que
se le pase. Se pueden conseguir cosas muy
interesantes. ■
INTRODUCCIÓNPrimeros Pasos
9PYTHONW W W. L I N U X - M A G A Z I N E . E S
[1] Python: http://www.python.org
Recursos
01 #!/usr/local/bin/python
02
03 #Importamos dos módulos
04 #random [que hace]
05 #y sys [que hace]
06 import random
07 import sys
08
09 no_palabra = “\n”
10 w1 = no_palabra
11 w2 = no_palabra
12
13 # GENERAMOS EL DICCIONARIO
14 dict = {}
15
16 for linea in sys.stdin:
17 for palabra in linea.split():
18 dict.setdefault( (w1, w2), []).append(palabra)
19 w1 = w2
20 w2 = palabra
21
22 # Fin de archivo
23 dict.setdefault((w1, w2), []).append(no_palabra)
24
25 # GENERAMOS LA SALIDA
26 w1 = no_palabra
27 w2 = no_palabra
28
29 # puedes modificarlo
30 max_palabras = 10000
31
32 for i in xrange(max_palabras):
33 nueva_palabra =random.choice(dict[(w1, w2)])
34
35 if nueva_palabra == no_palabra:
36 sys.exit()
37
38 print nueva_palabra;
39
40 w1 = w2
41 w2 = nueva_palabra
Listado 4: markov.py Genera un Texto No-Tan-Aleatorio
Para empezar, debemos saber por qué Python es obligatorio hacerlo de unamanera. Como es un lenguaje tipo script, debemos poner ‘#!’ seguido de laejecución del programa. También tenemos a nuestra disposición los operadoreshabituales de otros lenguajes: +, -, *, /, etc. Y una vez que sabemos cómomanejar operadores y asignaciones, se pueden mezclar: diccionarios de listasde diccionarios, diccionarios de listas, listas de Python también vienenprovistas de muchas más opciones que sus semejantes en otros lenguajes. Porejemplo, vamos a definir una lista que guarde una serie de espacios (logradospulsando la tecla TABULADOR) y una sentencia. Esos espacios son vitales, yaque marcan la existencia de un texto que, aunque carece de sentido,normalmente se corresponde con la que Python trata las listas dediccionarios, diccionarios de listas, listas de Python resultan tanpotentes, es que nos permiten referenciar a un elemento en la sangría de laOOP. - Es potente. Realizar un programa de 1000 lineas en Java, en Python esobligatorio hacerlo de una manera. Como es un diccionario con dos palabrascomo índice y las palabras que les siguen en el que muchas entradasreferenciarán a una lista de más de un bloque de código Python. Ese bloqueaparecerá en bucles, funciones o métodos. Si nos fijamos bien en la sangría dela memoria a bajo nivel como C con el tamaño del texto que se usa en el Listado1.
Listado 5: Salida de markov.py
INTRODUCCIÓN Ficheros
10 PYTHON W W W. L I N U X - M A G A Z I N E . E S
En nuestro primer artículo vimos algo
sobre cómo trabajar con objetos en
Python. Fue muy simple, pero ya nos
daba la posibilidad de organizar nuestro
código en torno a ellos. Python hace un
uso extensivo de los objetos en sus APIs,
y especialmente del control de errores
mediante excepciones, lo que nos da la
opción de lanzarlas cuando algo va mal.
Una excepción es un mensaje que pode-
mos capturar cuando se ejecuta cierta
función o método y aparece un error de
algún tipo. Normalmente controlamos
estos errores mediante el valor devuelto
por la función (como por ejemplo en C).
Esta técnica es engorrosa, pero al igual
que todo, tiene sus virtudes y sus des-
ventajas. Pero Python hace uso de las
excepciones en su lugar.
Cuando una función genera una
excepción, decimos que eleva una excep-
ción. Es muy normal tener que controlar
las excepciones en las operaciones que
realicemos con recursos que pueden no
estar disponibles. Por eso las vamos a
ver, puesto que aquí vamos a trabajar
con archivos y conexiones a Internet.
Crearemos un objeto que gestione un
recurso que puede no estar disponible.
En este caso el objeto gestiona una varia-
ble (véase el Listado 1).
Alguien puede crear un objeto de la
clase obj_variable y llamar al método
set_variable(23), pero ¿cómo puede estar
seguro de que la variable var tiene el
valor 23 después de la llamada? Puede
que var no tuviese el valor inicial de 0,
porque otra llamada anterior ya podría
haberla asignado. Lo único que podría-
mos hacer es llamar a reset_variable() y
así asegurarnos de que nuestro valor sea
asignado, pero entonces destruiríamos el
valor anterior y no sabríamos qué podría
pasar.
Por lo tanto, necesitamos un meca-
nismo de comunicación para darle a
conocer al usuario que esa variable ya
está asignada. Esto lo podemos hacer
con las excepciones.
En el Listado 2 aparece una clase que
hereda de la clase Exception llamada
Var_Asignada. Cuando en la clase
obj_variable intentamos asignar un valor
a la variable var y ésta no es 0, entonces
se dispara, se eleva, la excepción
Var_Asignada. Si no controlamos la por-
ción de código en la que se encuentra
set_variable() y aparece una excepción,
el programa se detendrá y acabará.
La idea detrás de las excepciones es
que es posible tratarlas y evitar males
mayores, pudiendo en ocasiones incluso
recuperarnos de ellas. Para ello está la
estructura try—except, con la cual rodea-
mos el código que puede disparar excep-
ciones (Véase el Listado 3).
A partir de ahora, y hasta que no expli-
quemos con más profundidad el tema de
las excepciones, cuando digamos que
una función genera una excepción, signi-
ficará que ese código deberá estar rode-
ado con una estructura try—except.
Trabajo con FicherosYa que hemos conseguido cierta soltura
con los conceptos de objetos en Python,
ahora vamos a ver cómo se manejan los
accesos a ficheros en él.
Para acceder a un fichero, primero
necesitamos crear un objeto file. El
objeto file es parte de la librería base de
Python, así que no es necesario importar
ninguna librería.
>>> archivo = file(‘texto.txt’)
Por definición, file abre los ficheros en
modo de sólo lectura. Eso significa que si
el fichero no existe, obtendremos un
error. Para verificar si el fichero existe
podemos usar la función exists() de la
librería os.path.
>>> import os.path >>>
os.path.exists(‘texto.txt’) True
>>> os.path.extsts(‘algo-
peludo-y-feo.txt’) False
Por lo tanto, si vamos a abrir un
fichero, podemos asegurarnos de que ya
existe.
Si en lugar de leerlo lo que queremos
es crearlo, deberemos invocar al cons-
tructor de file con los parámetros:
>>> archivo = file(‘texto.txt’,U
‘w’)
Este segundo parámetro opcional nos
permite definir el tipo de acceso que
vamos a realizar al fichero. Tenemos
varias posibilidades: podemos leer (r),
escribir (w), añadir al final del fichero
(a) y también tenemos el acceso de lec-
tura/ escritura (r+w). Disponemos tam-
bién del modificador b para indicar
acceso binario. Por defecto, Python con-
Siguiendo con nuestro paseo por Python, vemos
características básicas, y concretamente en este
artículo, el tratamiento de ficheros. Por José Mari Ruíz
Rusla
n O
linchuk - 1
23R
F.c
om
Manejo básico de ficheros
ÁlbumFotográfico
sidera todos los ficheros de texto. Vemos
todas las combinaciones en el Listado 4.
Si todo ha ido bien, con cualquiera de
estas llamadas tendríamos en archivo un
objeto que gestiona el archivo indicado.
Ahora podemos operar sobre él.
Las operaciones más típicas son las de
leer desde el archivo y escribir en él. Para
ello, el objeto file dispone de los métodos
read(), readline(), write() y writeline().
Todos ellos operan con cadenas de carac-
teres: readline() y writeline() trabajan
con líneas de texto (acabadas en retorno
de carro), mientras que read() y write()
lo hacen con cadenas sin restricciones.
Lo que vemos en el Listado 5 son
algunas manipulaciones sobre un
fichero. Lo primero que tenemos que
hacer es crear el fichero, para lo cual lo
abrimos en modo de escritura, w, que lo
creará o truncará el existente (lo borrará
para crearlo de nuevo. Si lo hubiéramos
querido añadir al final, habríamos usado
a). Posteriormente escribimos en él una
cadena con un retorno de carro en mitad
(para hacer nuestras pruebas) y cerra-
mos el fichero. Es importante cerrar los
ficheros cuando dejemos de usarlos,
pero en este caso la razón para cerrarlo
es que vamos a volver a abrirlo en modo
de lectura.
Ahora volvemos a abrir el fichero en
modo de lectura, y leemos 4 bytes que
almacenamos en la variable cadena.
Cuando leemos con read(), avanzamos
en el fichero, siendo esta la razón de que
readline() que viene a continuación lea
la cadena “ mundo\n” en lugar de “Hola
mundo”. También vemos que se para en
el retorno de carro en lugar de continuar.
El segundo readline() ya nos permite
leer la cadena “Adiós mundo”.
Pero… ¿qué ocurriría si en una de las
lecturas nos encontrásemos con el fin de
fichero? En el caso de que leyésemos una
cadena con el fin de fichero (EOF), al
final simplemente nos quedaríamos con
la cadena hasta el EOF. En cambio, si
sólo leemos el EOF, entonces obtenemos
una null. Esto es importante para com-
probar que hemos acabado con el
fichero. Así, un bucle que escriba por
pantalla el contenido del fichero compro-
baría en cada vuelta si la cadena que
devuelve readline() es null.
Ahora que ya sabemos crear archivos,
tenemos que aprender a borrarlos. Esto
se realiza mediante la función remove()
de la librería os. Esta función acepta la
ruta de un fichero y lo borra. Si en lugar
de un fichero le pasamos un directorio
elevará una excepción OSError.
>>> import os
>>> os.remove (texto.txt)
>>>
Directorios y Sistema de FicherosCon estos pocos métodos tenemos ya a
nuestro alcance la manipulación básica
de ficheros. Pero vamos a necesitar para
nuestro programa la posibilidad de crear
directorios. ¿Cómo lo haremos? Pues
mediante la función mkdir(), que acepta
una cadena y crea un directorio con ese
nombre. Si queremos crear un directorio
que esté dentro de otros directorios tam-
bién nuevos tenemos que usar make-
dirs(). Ambas funciones pertenecen al
módulo os, por lo que para usarlas ten-
dremos que hacer:
>>> import os
>>> os.mkdir(‘uno’)
>>> os.makedirs(‘dos/tres’)
Para borrar esos directorios usaremos las
funciones rmdir() y removedirs(). La pri-
mera borra un directorio, mientras que
la segunda borra una ruta de directorios.
Vamos a ver esto con más detenimiento.
>>> os.rmdir(‘uno’)
>>> os.removedirs(‘dos/tres’)
rmdir() borrará el directorio “uno”, que
no contiene ningún otro objeto en su
interior (ni directorios, ni ficheros). En
caso de tenerlo, la llamada devolvería un
error. La función removedirs() comenza-
ría a borrar desde el directorio que está
más a la derecha de la ruta (“tres”) hacia
el que está más a la izquierda (“dos”).
Pero imaginemos que dentro de “dos”
también hay un directorio “cuatro”.
Entonces se borraría el directorio “tres”,
y cuando la función fuese a borrar el
directorio “dos”, se encontraría con que
no puede porque existe dentro de él un
directorio llamado “cuatro” y pararía.
Imaginemos ahora que necesitamos
cambiar el directorio en el que estamos
trabajando. En el momento de arrancar
el programa, el llamado “directorio de
trabajo” – es decir, el directorio donde de
manera predeterminada se realizarán
todos los cambios – es el directorio que
alberga el programa o bien el directorio
desde el que se ejecutó. Pero, claro, no
siempre querremos que el programa uti-
lice ese directorio.
Hay que tener en cuenta que, a no ser
que utilicemos rutas absolutas, cualquier
referencia a un fichero se tomará con
relación al directorio de trabajo inicial.
Para poder cambiar el directorio de tra-
bajo, el módulo os tiene la función
chdir(). Si lo invocamos dentro de nues-
tro programa:
>>> os.chdir(‘/tmp’)
Desde ese momento, cualquier referencia
a un fichero será direccionada a “/ tmp”.
Ahora podemos:
• abrir, cerrar, modificar ficheros
• crear, eliminar un directorio
• cambiar el directorio de trabajo
Vamos a ir un poco más allá.
Llamadas a Otros ProgramasA veces es más sencillo usar una utilidad
del sistema operativo que crearla noso -
INTRODUCCIÓNFicheros
11PYTHONW W W. L I N U X - M A G A Z I N E . E S W W W . L I N U X - M A G A Z I N E . E S
01 class obj_variable:
02 __init__(this):
03 var = 0
04
05 set_variable(this, valor):
06 if (var == 0):
07 var = valor
08
09 reset_variable(this):
10 var = 0
Listado 1: Una Clase Python
01 class Var_Asignada(Exception):
02 “”“ Excepción que se dispara alintentar asignar una variable yaasignada en obj_variable”“”
03 pass
04
05 class obj_variable:
06 “”“ Administra una variable “”“
07 def __init__(self):
08 self.var = 0
09
10 def set_variable(self, valor):
11 if (self.var == 0):
12 self.var = valor
13 else:
14 raise Var_Asignada
15 def reset_variable(self):
16 self.var = 0
17
18 a = obj_variable()
19 a.set_variable(12)
20 a.set_variable(34)
Listado 2: Uso de Excepciones
INTRODUCCIÓN Ficheros
12 PYTHON W W W. L I N U X - M A G A Z I N E . E S
En este apartado vamos a comenzar
con lo básico. Queremos traer un recurso
de la red a nuestra máquina, y para ello
emplearemos una URL del estilo http://
www. algunaweb. algo/ imagen. jpg. Pero
primero necesitamos crear una conexión
con el servidor.
Para ello vamos a utilizar la librería
httplib que viene de serie con Python.
Esta librería nos permite establecer una
conexión con un servidor http y man-
darle comandos. Los comandos http
son simples, y de todos ellos sólo nos
interesa uno, el comando GET. Cuando
accedemos a un servidor http, por
ejemplo para ver una página web, lo
que hacemos es pedirle objetos. Esto se
hace mediante el comando GET
<objeto>. Por ejemplo, si queremos la
página index.html de la web http://
www. python. org, primero conectamos
con el servidor http y después, una vez
conectados, le enviamos el comando
GET index.html. En ese momento el
servidor nos devuelve por el mismo
canal el contenido del archivo
index.html.
Dicho así parece muy fácil, pero es
una tarea que en un lenguaje de más
bajo nivel requeriría gran cantidad de
librerías y control de errores.
Lo primero es importar la librería
httplib. Creamos entonces una conexión
con el host en cuestión y pedimos el
archivo index.html. Esa conexión genera
una respuesta. La respuesta está for-
mada por varias partes, entre ellas un
código numérico (como el famoso 404),
un texto que describe el error y una
conexión al archivo que pedimos. En el
caso de una conexión correcta recibire-
mos un 200, un OK y una conexión con
el fichero. De esa conexión lee-
mos con read() el contenido y
lo almacenamos en una varia-
ble que llamamos dato. Enton-
ces podremos cerrar la cone-
xión como si de un fichero se
tratara.
En ese momento ya tenemos
la información que queríamos
en dato y el canal cerrado. No
es muy difícil, ¿no? Veremos un
ejemplo en el programa final de
este artículo.
Paso de ParámetrosEstamos acostumbrados a
poder pasar parámetros a los
programas. En UNIX es algo común.
Pero… ¿cómo podemos obtener los pará-
metros de ejecución en Python? De
nuevo tenemos que recurrir a una libre-
ría: la librería sys.
sys nos proporciona el acceso a los
argumentos a través de su variable argv.
Esta variable es en realidad una lista, por
lo que podemos obtener los argumentos
accediendo a las posiciones de la misma.
La posición 0 contiene el nombre del
programa que estamos ejecutando y, a
partir de la posición 1, encontraremos
los parámetros pasados. Al ser una lista,
podemos conocer la cantidad de paráme-
tros llamando a len().
ProgramaAhora es el momento de poner todo lo
aprendido en práctica con un programa
que puede ser útil. En este caso vamos a
crear uno que realizará las siguientes
tareas:
• El programa aceptará un parámetro de
entrada que le indicará el nombre de
un fichero.
• El programa abrirá ese fichero y lo
leerá línea por línea. Cada línea del
fichero será la dirección URL de una
imagen.
• Cada URL será introducida dentro de
una lista para su uso posterior.
tros, como por ejemplo, un procesado
usando tuberías en UNIX. Puede que
simplemente tenga que acceder a alguna
información como la que nos da uname.
El caso es que siempre es importante
tener la posibilidad de ejecutar otros pro-
gramas desde nuestro programa Python.
Para ello usamos la función system del
módulo os. Por ejemplo:
>>> import os
>>> os.system (‘uname -a’)
Linux rachel 3.1.2-1.fc16.x86_64
#1 SMP Tue Nov 22 09:00:57 UTC 2011
x86_64 x86_64 x86_64 GNU/Linux
0
>>>
El parámetro que le pasamos a system es
una cadena con la instrucción Bash (en
este caso) y sus switches y flags. system
nos devuelve la salida de la instrucción (
Linux rachel 3.1.2-1.fc16.x86_64 #1 SMP
Tue Nov 22 09:00:57 UTC 2011 x86_64
x86_64 x86_64 GNU/ Linux) y el estado de
salida resultante de la ejecución de la ins-
trucción (0 – recuérdese que 0 indica que
la instrucción ha acabado sin errores).
Python y la WebPython posee gran cantidad de librerías
para trabajar con recursos de Internet.
De hecho, Django [1] , un servidor de
aplicaciones con gran éxito, está creado
en Python y hace uso de todas sus carac-
terísticas. Mailman [2] o Bittorrent [3]
son también buenos ejemplos.
Debido a su flexibilidad, Python es
usado como lenguaje de implementación
para multitud de aplicaciones de red así
como aplicaciones distribuidas. Por eso,
no es de extrañar que Python suela ser el
lenguaje en el que se implementan
muchas de las más novedosas tecnolo-
gías de red.
01 >>> try:
02 ... set_variable(12)
03 ... set_variable(34)
04 ... except:
05 ... print “ERROR: Se ha intentadoasignar”
06 ... print “un valor a unavariable ya asignada”
07 ...
08 ERROR: Se ha intentado asignar
09 un valor a una variable yaasignada
10 >>>
Listado 3: Más Excepciones
01 >>> archivo = file(‘/tmp/texto.txt’,’w’)
02 >>> archivo.write(“Hola mundo\nAdiosmundo”)
03 >>> archivo.close()
04 >>>
05 >>> archivo = file(‘/tmp/texto.txt’,’r’)
06 >>> cadena = archivo.read(4)
07 >>> cadena
08 ‘Hola’
09 >>> cadena = archivo.readline()
10 >>> cadena
11 ‘ mundo\n’
12 >>> cadena = archivo.readline()
13 >>> print cadena
14 ‘Adios mundo’
15 >>> archivo.close()
Listado 5: Lectura y Escritura de Ficheros
01 archivo = file(‘texto.txt’,’r’)
02 archivo = file(‘texto.txt’,’w’)
03 archivo = file(‘texto.txt’,’a’)
04 archivo =file(‘texto.txt’,’r+w’)
05 archivo =file(‘texto.txt’,’r+b’)
06 archivo = file(‘texto.txt’,’rb’)
Listado 4: Acceso a Ficheros
INTRODUCCIÓNFicheros
13PYTHONW W W. L I N U X - M A G A Z I N E . E S
001 #!/usr/bin/python
002
003 # ---NOTA--------------------------------------
004 # El fichero que debe ser pasado como argumento
005 # debe consistir en un listado con una url por
006 # línea.
007 # ---------------------------------------------
008
009 class Lista_URLs:
010 “”“Recibe un fichero y carga sus cadenas en unalista. Provee de métodos para obtener de nuevo lascadenas desde la lista.”“”
011
012 def __init__(self,nombre):
013 # La lista donde guardaremos las URLs
014 self.lista= []
015 # El contador que usaremos para comprobaciones
016 self.contador = 0
017
018 # pasamos el nombre del fichero menos el últimocarácter
019 self.archivo = file(nombre)
020 self.cadena = self.archivo.readline()
021
022 while(self.cadena != ‘\n’):
023 #Metemos la cadena en la lista
024 self.lista.append(self.cadena)
025 self.cadena = self.archivo.readline()
026 self.archivo.close()
027
028
029 def rebobina(self):
030 # Hace que se comience de nuevo
031 # por el principio en la lista.
032 self.contador = 0
033
034
035 def siguiente(self):
036 # Devuelve el siguiente elemento o
037 # ‘’ en caso de llegar al final.
038 if ( self.contador >= len(self.lista)):
039 return ‘’
040 else:
041 self.valor = self.lista[self.contador]
042 self.contador = self.contador + 1
043 return self.valor
044
045 def fin(self):
046 # Comprueba que hemos llegado al final
047 # de la lista. Preguntamos si hemos llegado
048 # al final antes de avanzar.
049 return (self.contador == len(self.lista))
050
051 def crea_directorio(cadena):
052 # Comprueba si el directorio especificado por
053 # cadena existe, en caso contrario lo crea
054 # y cambia el directorio de trabajo
055 # al directorio creado.
056
057 componentes = cadena.split(‘.’)
058
059 if(os.path.exists(componentes[0])):
060 print “Error: el directorio ya existe”
061 sys.exit()
062 else:
063 # Creamos el directorio
064 os.makedirs(componentes[0])
065 os.chdir(componentes[0])
066 print ‘Creando directorio ‘ + componentes[0]
067
068 def descarga_urls(lista):
069 # Recorre la lista de urls usando el objeto
070 # Lista_URLs, las descarga y después las
071 # guarda en ficheros con el mismo nombre que
072 # el de la imagen.
073
074 lista.rebobina()
075
076 while( not lista.fin() ):
077 url = lista.siguiente()
078
079 # dividimos la url en dos partes
080 # lo que descargamos y la url http
081
082 # Componentes es una lista que contiene
083 # las cadenas resultantes de trocear la
084 # cadena de texto de la URL usando ‘/’
085 # como separador. Por ejemplo:
086 # http://www.python.org/index.html
087 # componentes = [‘http:’, ‘’, ‘www.python.org’,
088 # ‘index.html’]
089 componentes = url.split(‘/’)
090 servidor = componentes[2]
091
092 # Construimos la ruta de la imagen, que
093 # consiste en toda la ruta si eliminamos
094 # al servidor y a http://
095 ruta_imagen = ‘/’
096 for i in range( 3, len(componentes)):
097 ruta_imagen = ruta_imagen + ‘/’ + componentes[i]
098
099 # Descarga el fichero y lo guarda con el nombre.
100 # El nombre se saca de la URL.
101 # url[:-1] es la cadena url menos el último carácter.
102 print ‘Descargando imagen: ‘ + url[:-1]
103 conexion = httplib.HTTPConnection(servidor)
104 conexion.request(“GET”, ruta_imagen)
105 respuesta = conexion.getresponse()
106 # datos contiene ahora la imagen y la guardamos
107 datos = respuesta.read()
108 conexion.close()
109
110 # el nombre del fichero es el último elemento
111 # de la lista componentes
112 nomb_fichero = componentes[len(componentes) -1]
113 # eliminamos el \n final
114 nomb_fichero = nomb_fichero[:-1]
115
116 # Abrimos el fichero, escribimos y cerramos
117 archivo = file(nomb_fichero ,’w’)
118 archivo.write(datos)
119 archivo.close()
120
121 def genera_index(lista):
122
123 # Crea un fichero index.html.
124 # Genera la cabecera, recorre la lista de URLS
125 # y por último escribe el pie.
126 # Es posible mejorarlo introduciendo separadores
127 # o títulos entre las imágenes ;)
128
Listado 6: Agarrafotos.py
• Leer las URLs.
• Crear Directorio y cambiar el directo-
rio de trabajo.
• Descargar las URLs.
• Generar el archivo HTML.
Seguiremos estos puntos para crear las
funciones. Las URLs las almacenaremos
en una lista. ¿Deberíamos usar objetos?
Esta es una de las cosas maravillosas que
ofrece Python: NO estamos obligados a
usar objetos. Y no digo que los objetos
sean malos, sino que en ocasiones pue-
den llegar a ser engorrosos. Por ejemplo,
podríamos crear un objeto Lista_URLs
que aceptase como parámetro en su
constructor el nombre de un fichero y
que después nos permitiese ir cogiendo
las URLs una detrás de otra. También
podemos hacer lo mismo usando una
función que cargue las URLs en una
variable global. Aquí vamos a hacerlo
con un objeto. Es en este momento
cuando se deja al lector que explore la
posibilidad de sustituir el objeto por una
variable global y las funciones de lista.
Este programa es muy simple, pero de
nuevo retamos a los lectores a mejorarlo
y a introducirle, por ejemplo, control de
excepciones.
Suerte. ■
• Una vez que hayamos acabado de leer
el fichero, lo cerraremos y entraremos
en la segunda parte del programa.
• Crearemos un directorio con el nom-
bre del archivo que nos hayan dado.
• Cambiaremos el directorio de trabajo a
ese directorio.
• Descargaremos cada una de las URLs
dentro del directorio.
• Generaremos un archivo index.html
que muestre las imágenes.
¿Mucho trabajo? Para eso están los pro-
gramas. Evidentemente no realizaremos
todas las comprobaciones que serían
necesarias, ya que en tal caso el pro-
grama se alargaría demasiado, por lo que
se deja al lector la opción de incluir
mejoras. Pensemos ahora en su diseño.
Tenemos varias partes:
• Comprobar y almacenar la opción con
el nombre del archivo.
INTRODUCCIÓN Ficheros
14 PYTHON W W W. L I N U X - M A G A Z I N E . E S
[1] La infraestructura Django para aplica-
ciones web:
https:// www. djangoproject. com/
[2] El programa administrador de listas
de correo Mailman: http:// www. gnu.
org/ software/ mailman/
[3] El programar para administración de
Torrente BitTorrent:
http:// bittorrent. com/
Recursos
129 print ‘Generando índice index.html’
130
131 archivo = file(‘index.html’,’w’)
132
133 # Cabecera
134 archivo.write(‘<html>\n’)
135 archivo.write(‘<head>\n’)
136 archivo.write(‘<title> Imagenes </title>\n’)
137 archivo.write(‘</head>\n’)
138 archivo.write(‘<body>\n’)
139 archivo.write(‘<h1>Imagenes</h1>\n’)
140 archivo.write(‘<ul>\n’)
141
142 # siempre antes de recorrer:
143 lista.rebobina()
144 url = lista.siguiente()
145
146 # Dividimos la URL para poder utilizar
147 # partes de ella.
148 componentes = url.split(‘/’)
149 imagen = componentes[len(componentes) - 1]
150
151 # Recorremos las urls
152 while( url != ‘’):
153 # Imagen en HTML
154 archivo.write(‘<li><img src=\”‘+ imagen+’\”></img></li>\n’)
155 url = lista.siguiente()
156 componentes = url.split(‘/’)
157 imagen = componentes[len(componentes) - 1]
158
159 # ... y por último el pie.
160
161 archivo.write(‘</ul>\n’)
162 archivo.write(‘</body>\n’)
163 archivo.write(‘</html>\n’)
164
165 archivo.close()
166
167 #------------------------------------------------
168 # Main
169 #------------------------------------------------
170
171 # Esta es la técnica estándar para organizar el
172 # código en Python, se usa la siguiente construcción
173 # como punto de arranque.
174
175 if __name__ == ‘__main__’:
176
177 import httplib
178 import os
179 import os.path
180 import sys
181
182 # Comprobamos los argumentos...
183
184 if len(sys.argv) == 2:
185 #Pasamos el fichero al constructor
186 lista = Lista_URLs(sys.argv[1])
187
188
189 crea_directorio(sys.argv[1])
190
191 descarga_urls(lista)
192
193 genera_index(lista)
194
195 elif len(sys.argv) == 0:
196 # Vaya, han ejecutado sin poner argumentos...
197 # les recordaremos como va esto ;)
198 print ‘La sintaxis del programa es:\n’
199 print sys.argv[0] + ‘ archivo\n’
200 print ‘El archivo debe contener una URL por línea’
201
202 else:
203 # Alguien se ha quedado corto y se ha pasado
204 # con el número de argumentos.
205 print “ERROR: la sintaxis es “ + sys.argv[0] + “<fichero>”
Listado 6: Agarrafotos.py (Cont.)
Equivocarse es humano, y a pesar de
todo el mito que rodea a los programado-
res, hasta el mejor de ellos comete erro-
res diariamente. En muchas ocasiones es
mucho más complicado eliminar un BUG
que crear el propio programa. Cuenta le
leyenda que el nombre de BUG viene de
la misma palabra que en inglés significa
bicho. Dicen que los primeros ordenado-
res eran grandes máquinas que genera-
ban gran cantidad de calor, por lo que
innumerables insectos y otras alimañas
se introducían en ellos. De vez en
cuando, alguno tocaba dos cables y que-
daba frito, provocando un fallo en el sis-
tema. Actualmente se conoce como BUG
a todo error o situación no controlada
que impida a un programa realizar tu
tarea con normalidad. Lo cierto es que
estamos bastante acostumbrados a que
los BUGS sean parte de nuestra vida.
Ventanas que no se cierran, programas
que consumen todo el espacio en memo-
ria o videojuegos que se quedan blo-
queados.
Python es un lenguaje dinámico, como
muchos otros. La principal ventaja es
que nos permite programar a alto nivel,
desentendiéndonos de toda la gestión de
recursos a bajo nivel que hace tan
pesada la programación en otros lengua-
jes como por ejemplo C. Pero no todo el
monte es orégano. También hay una
parte negativa: Python no es un lenguaje
demasiado estricto. Podemos hacer lo
que queramos con las variables sin que
el intérprete se queje hasta el último
momento. Esta característica impide la
posibilidad de verificar automáticamente
todo el código en el momento en que es
compilado. Un código totalmente erró-
neo, en el que por ejemplo se suman
letras y números, puede pasar desaperci-
bido en nuestro programa hasta el día
que se ejecuta y genera un error que
dejará al usuario con la boca abierta, y
ciertas dudas sobre nuestra valía como
programadores.
Casi a la vez que surgieron los lengua-
jes de programación aparecieron unos
programas que han ido unidos a ellos:
los debuggers. Exiten muchos debuggers
diferentes. GNU desarrolló DDD, pero
hace tiempo que no se ve actividad en
este proyecto (ver Recurso [1]). Valgrind
ha conseguido mucha fama en proyectos
que emplean C++ ( ver Recurso [2]).
En este artículo vamos a echar un vis-
tazo a las herramientas que podemos
usar para localizar los fallos en nuestros
programas Python, y en particular a la
que viene de serie con Python: el PDB,
Python DeBugger (podemos ver la docu-
mentación de PDB en el Recurso [3]).
Un Bug, Dos Bugs, Tres Bugs…Los lenguajes dinámicos, como decía-
mos antes, tienen sus propias virtudes y
desventajas. Los programadores más
duros suelen decir que programar con
lenguajes dinámicos es como jugar con
juguetes: no hay bordes cortantes con
los que cortarse ni partes pequeñas con
las que atragantarnos. Vamos, que son
poco menos que una versión infantil de
los lenguajes “serios”. Lo cierto es que
hay mucha gente que no entra en estos
debates. Yo, por lo menos, prefiero hacer
un programa tan rápido como sea posi-
ble y espero que funcione casi a la pri-
mera.
¿De dónde salen los BUGS? Lo más
probable es que haya siempre una varia-
ble implicada. La explicación es simple:
las variables son la única parte del pro-
grama que realmente no controlamos.
Mientras el resto del código hace exacta-
mente lo que le indicamos que haga, por
ejemplo abrir un fichero, en las variables
suceden todo tipo de cosas mientras el
INTRODUCCIÓNDebugging
15PYTHONW W W. L I N U X - M A G A Z I N E . E S
Da igual lo buenos programadores que seamos, tarde o temprano daremos con ese BUG que
será nuestro peor enemigo. Veamos cómo podemos emplear herramientas para derrotarlo
con mayor facilidad. Por José María Ruíz
Eliminación de bugs de programas Python
DesparasitandoSerpientes
Sebastia
n K
aulitz
ki - 1
23R
F.c
om
INTRODUCCIÓN Debugging
16 PYTHON W W W. L I N U X - M A G A Z I N E . E S
programa está en ejecución. El problema
es que no vemos esas variables mientras
el programa está funcionando, así que
tenemos que imaginarnos qué está
pasando.
En condiciones ideales se puede per-
der el tiempo tratando de localizar los
fallos a ojo de buen cubero, pero bajo
estrés y con plazos, toda ayuda es poca.
Python además nos permite almacenar
cualquier valor dentro de una variable.
Las variables en los lenguajes dinámicos
como Python son casi mágicas. En ellas
podemos almacenar un número:
>>> mivariable = 32
Y punto seguido almacenar un objeto:
>>> class Persona:
... def __init__(this,nombre):
... this.nombre = nombre
...
>>> mivariable = PersonaU
(‘Carlos’)
Y sigue siendo la misma mivariable, pero
de alguna manera su naturaleza ha cam-
biado. Ahora imagina que esta situación
ocurre en un programa que has creado.
Mientras tecleas piensas, “‘mivariable’
contiene una distancia” y operas con ella,
sólo que, sin que te des cuenta, en reali-
dad mivariable contiene un objeto de la
clase Persona ¿qué pasara si intentas
sumarle 18?
>>> a + 18
Traceback (most recent call last):
File “<stdin>”, line 1, in ?
TypeError: unsupported operand
type(s) for +: ‘instance’ and ’int’
>>>
¡ERROR! El intérprete de Python nos
advierte de que algo no marcha bien:
hay un error de tipo, no es posible
sumar un número entero y una instancia
de un objeto tal como hemos definido la
clase. Desagradable ¿verdad? Por lo
menos este tipo de BUG, que es tan fácil
de encontrar que el propio intérprete de
Python lo encuentra. ¿Qué ocurría si el
programa no fallase, sino que no nos
diese el resultado esperado? ¿Y si el pro-
grama le dijese a un cliente que su edad
actual es -323134.32 años? Este es sin
duda el peor tipo de BUG existente: el
semántico.
El intérprete de Python es perfecto
localizando errores sintácticos, aquéllos
que tienen que ver con las propias pala-
bas que escribimos. Si hacemos referen-
cia a una variable que no está definida,
Python trata de buscar su valor, ve que
no existe la variable y se queja.
Los errores semánticos son harina de
otro costal, porque se refieren a fallos que
aparecen debido a que no se entiende lo
que está pasando. Un ejemplo simple es
el que hemos visto antes, cuando hemos
tratado de sumar una variable con una
instancia de un objeto que no responde a
la suma con un número.
Solucionar un BUG semántico puede
llevar desde segundos a años. De hecho,
existe mucho software, tanto comercial
como libre, con BUGS que no han sido
resueltos en años. Ahora que el pro-
blema ha sido planteado con más deteni-
miento, conviene ver con qué arsenal
contamos en nuestra batalla contra los
BUGS.
Depurando CódigoDigamos que estamos haciendo un script
para mostrar, usando como caracteres las
vacaciones que han escogido una serie de
empleados de una empresa. Como no
queremos que el código sea largo ni
demasiado complicado, lo hemos redu-
cido al mínimo. La función tomará una
lista de tuplas, que representa las vaca-
ciones de una persona en un mes. Cada
tupla representa un periodo de vacacio-
nes, con una fecha de inicio y una fecha
de fin. La idea es muy simple, aceptamos
esa lista y después devolvemos una
cadena donde los días de trabajo se
representan por un espacio, y los de
vacaciones con un “#”. No es muy com-
plicado ¿verdad?
Así que nos ponemos manos a la obra,
es algo sencillo, no nos llevará ni 10
minutos, acabamos escribiendo el
código del Listado 1. Pero nos interrum-
pen antes de probar la función, y
cuando volvemos al ordenador y ejecu-
tamos un programa de prueba ¡se queda
bloqueado! ¡No puede ser!, en tan pocas
líneas de código no puede haber un
error tan grave. Después de unos minu-
tos de frustración, abandonamos la ins-
pección visual y pasamos a trabajar
PDB.
PDB es el Python DeBugger y viene de
serie con Python. Eso está muy bien,
porque en caso de necesitarlo siempre lo
tendremos a mano. A diferencia de otros
debuggers, PDB se puede usar como si
fuese una librería. Podemos integrarla en
nuestros programas y eliminarla cuando
ya los hayamos arreglado. Como es posi-
ble observar en el Listado 1, hemos
importado PDB y hemos pasado como
parámetro a pdb.run() una cadena en la
que invocamos la función vacaciones()
con un argumento acorde. Si ejecutamos
el programa, veremos lo siguiente en
nuestro terminal:
josemaria@linuxmagazine$U
./p.py
> <string>(1)?()
(Pdb)
Figura 1: ¿A quién dejaremos sin vacaciones?
01 #!/usr/local/bin/python
02
03 import pdb
04
05 def vacaciones (l):
06 cadena = “”
07 for i in range(1,31):
08 encontrado = False
09 max = len(l)
10 k=0
11 while(not(encontrado) ork<max):
12 rango = l[k]
13 inf,sup=rango
14 if ((i >= inf) and (i <= sup)):
15 encontrado = True
16 else:
17 k+=1
18
19 if (encontrado):
20 cadena += “#”
21 else:
22 cadena += “ “
23
24 return cadena
25
26 pdb.run(‘vaca-ciones([(1,3),(6,10)])’)
Listado 1: El Código Nefasto
Muy bien, el PDB comienza a hacer su
trabajo. En lugar de no hacer nada, como
haría la función vacaciones() si el pro-
grama se limitase a ejecutarla, entramos
en lo que parece el prompt del shell de
PDB. Esta shell tiene sus propios coman-
dos, no tiene nada que ver con Python.
Para ver los comandos disponibles pode-
mos emplear el comando h:
(Pdb) h
Documented commands (type help
<topic>):
================================
EOF break condition disable help
list q step w
a bt cont down ignore n quit
tbreak whatis
alias c continue enable j
next r u where
args cl d exit jump p return
unalias
09 b clear debug h l pp s up
Miscellaneous help topics:
==========================
exec pdb
Undocumented commands:
======================
retval rv
(Pdb)
¡Buff! estos son demasiados comandos.
Como suele ocurrir la primera vez que
un principiante en Linux pulsa dos
veces tabulador en BASH, lo que vemos
nos asusta. En este caso no son tantos
comandos (en un Linux estándar hay
miles de ejecutables), pero sí más extra-
ños. Debuggear es algo que SIEMPRE se
hace bajo presión, por lo que todo el
tiempo que ahorremos es oro. Así que
los creadores de PDB nos ahorran
segundos reduciendo los comandos a
letras.
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
or continue the previous listing.
With one argument, list 11 lines
starting at that line.
With two arguments, list the given
range;
if the second argument is less than
the first, it is a count.
(Pdb)
El comando l sirve para mostrar visual-
mente en qué parte del código estamos
en un momento dado; como nos pica la
curiosidad ejecutamos l:
(Pdb) l
[EOF]
Vaya, resulta que no hemos comenzado
aún, por lo que no estamos en ninguna
parte. El comando que probablemente
usemos más a menudo es s ¿Por qué?
pues porque es el comando que hace que
PDB avance una línea y la ejecute:
(Pdb) s
--Call--
> /home/josemaria/p.py(5)U
vacaciones()
-> def vacaciones (l):
Muy bien, comenzamos a ejecutar el
trozo de código Python que pasamos a
pdb.run(). Por el momento no pasa nada
interesante, aunque estaría bien ver qué
contiene la variable l, para ello podemos
usar el comando p que hace las funciones
de print:
(Pdb) p l
[(1, 3), (6, 10)]
Efectivamente, l contiene el parámetro
que hemos pasado a la función. Ya esta-
mos en ruta, así que avancemos unos
cuantos pasos más:
(Pdb) s
> /home/josemaria/p.py(6)
vacaciones()
-> cadena = “”
(Pdb) s
> /home/josemaria/p.py(7)
vacaciones()
-> for i in range(1,31):
(Pdb) s
> /home/josemaria/p.py(8)
vacaciones()
-> encontrado = False
(Pdb) s
> /home/josemaria/p.py(9)
vacaciones()
-> max = len(l)
(Pdb)
Hemos avanzado 4 pasos y puede que
nos hallamos perdido. ¿Dónde estamos?
¿Qué estamos haciendo? ¿Hacia dónde
vamos? El comando l resuelve todas
nuestras dudas:
(Pdb) l
4
5 def vacaciones (l):
6 cadena = “”
7 for i in range(1,31):
8 encontrado = False
9 -> max = len(l)
10 k=0
11 while(not(encontrado)
or k<max):
12 rango = l[k]
13 inf,sup=rango
14 if ((i >= inf) and
(i <= sup)):
(Pdb)
Ya me sitúo, acabamos de entrar en el
bucle for, avancemos un poco más:
(Pdb) s
> /home/josemaria/p.py(10)U
vacaciones()
-> k=0
(Pdb) s
> /home/josemaria/p.py(11)U
vacaciones()
-> while(not(encontrado) orU
k<max):
(Pdb)
INTRODUCCIÓNDebugging
17PYTHONW W W. L I N U X - M A G A Z I N E . E S
Figura 2: IDLE no es bello, pero es práctico.
Figura 3: El depurador de IDLE.
variable encontrado sea True. Si todo va
bien, el siguiente paso después de eva-
luar las condiciones del while será salir
del mismo y pasar a las siguiente instruc-
ción fuera del while:
(Pdb) s
> /home/josemaria/p.py(12)U
vacaciones()
-> rango = l[k]
(Pdb)
¿Pero qué ocurre aquí? Esto no debería
pasar. La única explicación posible es
que la condición del while esté… ¿es eso
un or? ¡Debería ser un and! Deberíamos
salir si hemos encontrado que el día per-
tenece a un rango, o si no quedan rangos
que comprobar. Ahí estaba nuestro BUG,
tres simples letras, se cambian y pro-
blema solucionado.
Ahora que ya sabemos lo que pasa,
sólo queda salir del debugger usando el
comando q. Nuestro nuevo código ya
está listo para ser usado (ver Figura 1).
Y Ahora de Forma Fácil¿No hay una manera más «moderna» de
conseguir esto mismo? Pues sí, gracias a
IDLE (ver Recurso [4]).
IDLE es el entorno de desarrollo que
viene, también, junto a Python. En la
Figura 2 se puede observar el aspecto
que tiene IDLE una vez que se ejecuta.
No es ninguna belleza, pero es práctico,
reemplaza al intérprete de comandos de
Python y simplifica algunas tareas.
En particular, estamos interesados en
cómo puede simplificar el debugging.
Para ello sólo tenemos que ir al menú
Debug que aparece en la barra de menú
de IDLE y activar Debugger. Aparecerá
una ventana como la que puede obser-
varse en la Figura 3. El lector no debe
extrañarse demasiado con esta nueva
ventana, viene a condensar en un solo
lugar todo lo que hemos visto sobre
PDB: hay un botón llamado STEP, que
nos permitirá avanzar en el código paso
a paso, y también hay un área llamada
Locals, donde iremos viendo el valor de
las variables que se vayan declarando en
el código, de forma que podremos ir con-
trolando la evolución del programa de un
solo vistazo.
Para ello sólo tenemos que cargar el
programa en IDLE y veremos cómo se
abre una especie de editor de textos
como el de la Figura 4, en el que debere-
mos seleccionar en el menú Run la
opción Run Module. Con este paso carga-
remos el fichero y comenzaremos a eje-
cutarlo. Como antes seleccionamos la
opción Debugger, IDLE se cargará en la
ventana de debugging y podremos
comenzar a visionar la evolución del
programa conforme pulsemos sobre el
botón Step. No es que IDLE sea un gran
avance respecto al uso de PDB, pero
desde luego simplifica el debugging.
ConclusiónLos debuggers no son una excusa para
crear programas sin fijarnos demasiado
en los problemas. Pero si no tenemos
claro qué está ocurriendo o si ya no
sabemos qué hacer, entonces un debug-
ger como PDB puede ayudarnos a tener
una imagen más clara de lo que pasa en
nuestro programa. Existen bugs que no
pueden cazarse con PDB, pero son tan
extraordinariamente raros, que es posi-
ble que jamás nos encontremos con
uno.
Los debuggers nos permiten progra-
mar sin miedo a no entender lo que esta-
mos haciendo, y es precisamente eso lo
que nos permitirá avanzar y aprender
más rápidamente. ¡Perdámosle el miedo
a los BUGS! ■
Bueno, llegamos a una encrucijada.
Cuando ejecutamos la función sin PDB,
parece como si el programa nunca aca-
base. Esto implica que hay algo que se
repite eternamente. En este programa hay
dos bucles, que son los únicos elementos
que pueden repetirse eternamente. El pri-
mero es un bucle for con un principio, 1,
y un fin, 31, por lo que podemos descar-
tarlo como culpable. El segundo sospe-
choso es ese bucle while, que en un pri-
mer momento no tiene porqué acabar,
puede que jamás pare. Si un bucle while
no para es porque las condiciones que lo
controlan siempre se dan. En este código
se supone que el if dentro del bucle while
hace que éste pare alguna vez, así que
algo debe fallar ahí dentro. Comencemos
comprobando los valores de las variables
que controlan el bucle:
(Pdb) p encontrado
False
(Pdb) p k
0
(Pdb) p max
2
(Pdb)
De acuerdo, todo parece en su sitio. Si
avanzamos un poco:
> /home/josemaria/p.py(12)
vacaciones()
-> rango = l[k]
(Pdb) s
> /home/josemaria/p.py(13)
vacaciones()
-> inf,sup=rango
(Pdb) s
> /home/josemaria/p.py(14)
vacaciones()
-> if ((i >= inf) and
(i <= sup)):
(Pdb) s
> /home/josemaria/p.py(15)
vacaciones()
-> encontrado = True
(Pdb) s
> /home/josemaria/p.py(11)
vacaciones()
-> while(not(encontrado) or
k<max):
(Pdb)
Resulta que hemos entrado en una tupla
que representa unas vacaciones, el día
representado por i pertenece a las vaca-
ciones. Por tanto, hemos hecho que la
INTRODUCCIÓN Debugging
18 PYTHON W W W. L I N U X - M A G A Z I N E . E S
[1] Documentación del depurador PDB:
http:// docs. python. org/ lib/ module-
pdb. html
[2] Valgrind captura errores que compro-
meten la memoria en:
http:// valgrind. org/
[3] Data Display Debugger:
http:// www. gnu. org/ software/ ddd/
[4] El entorno de desarrollo IDLE:
http:// www. python. org/ idle/
Recursos
Figura 4: El seudo-editor de IDLE.
Existe un gran misticismo en torno a
los conceptos de función lambda y al
concepto de cierre. Siempre que aparece
un nuevo lenguaje de programación
suele venir acompañado de una discu-
sión que demuestra que el nuevo len-
guaje es mejor porque incorpora alguno
de estos dos conceptos. Quien esté un
poco al tanto de los últimos «avances»
habrá escuchado decir que C# incorpora
las funciones lambda y que la próxima
versión java al fin las tendrá entre su
arsenal.
Las funciones lambda reciben su nom-
bre de la teoría del cálculo lambda de
Alonzo Church [1], que junto a Alan
Turing, sentaron las bases de la teoría de
computación. Mientras Turing utilizó en
su teoría una máquina abstracta e imagi-
naria a la que se llamó Máquina de
Turing, Alonzo utilizó un enfoque más
tradicional, creando una serie de reglas
que permitían realizar computaciones.
Su sistema requería de un tipo de fun-
ción especial, para la que usó la letra
lambda. Las funciones lambda que vere-
mos comparten sólo algunas de las
características que Alonzo definió para
las suyas, y podemos decir que lo que las
define es que son anónimas: las funcio-
nes lambda no tienen nombre.
¿Cómo es esto posible? ¿Para qué que-
rríamos algo así? Son sólo dos preguntas
que trataremos de resolver en este artí-
culo mientras desmitificamos un con-
cepto tan abstracto a primera vista.
Las Funciones LambdaLas funciones lambda son el concepto
más sencillo de los que vamos a ver en
este artículo. Python las soporta desde
hace un buen tiempo, y se encuentran
integradas en la librería base de Python.
También conocidas como funciones anó-
nimas, no son más que funciones sin
nombre que podemos crear en cualquier
momento y pasar como argumento a
otras funciones:
>>> a = lambda x : x + 1
>>> a(2)
3
>>>
Recordemos que en Python las variables
no son más que nombres que asignamos
a cosas y no contenedores de esas cosas.
Esta diferencia es vital para comprender
por qué podemos asignar una función a
una variable y posteriormente asignar un
valor a la misma (ver Figura 1).
En este sencillo ejemplo hemos creado
una función que suma el número 1 al
número que pasemos como argumento.
La definición de una función lambda
siempre comienza con la palabra lambda
seguida de los argumentos que vamos a
aceptar. Detrás de los argumentos usa-
mos el símbolo : para separar la defini-
ción del cuerpo de la función. Las fun-
ciones lambdas, al ser anónimas, deben
almacenarse en una variable si quere-
mos reutilizarlas, y se comportarán
AVANZADOFunciones Lambda
19PYTHONW W W. L I N U X - M A G A Z I N E . E S
Funciones lambda en Python
SinNombre
Python es un lenguaje de programación multiparadigma, y las
funciones lambda son parte fundamental de él, aunque como
veremos, existen buenas razones para no abusar de ellas.
Por José María Ruíz
© sty
lephoto
gra
phs - 1
23R
F.com
AVANZADO Funciones Lambda
20 PYTHON W W W. L I N U X - M A G A Z I N E . E S
como una función tradicional a la que
podremos llamar pasándole parámetros
entre dos paréntesis ().
Existen varias restricciones en el uso
de las funciones lambda. La primera es
que siempre deben devolver un valor.
Son funciones en el sentido estricto de
las matemáticas, aceptan valores, los
transforman y devuelven algún valor. En
Python podemos devolver varios valores
si lo deseamos:
>>> b = lambda x: (x,x+1)
>>> b(2)
(2,3)
>>>
La segunda restricción es que sólo pue-
den contener una expresión. Esta restric-
ción limita bastante el poder de las fun-
ciones lambda en Python. En Ruby, por
ejemplo, las funciones lambda (también
llamadas bloques) pueden contener tan-
tas expresiones como deseemos. En
Python se decidió añadir esta restricción
para que los desarrolladores terminaran
empleando las funciones lambda allí
donde una función tradicional podría
valerles (en Javascript es algo que se
hace habitualmente). Cuando violemos
una de estas restricciones, Python gene-
rará una excepción:
>>> c = lambda x: y = x+1
File “<stdin>”, line 1
c = lambda x: y = x+1
^
IndentationError: unexpected U
indent
En este caso hemos tratado de realizar
una asignación dentro de una función
lambda. Lo que sí podemos hacer es
pasar más de un parámetro a la fun-
ción:
>>> d = lambda x,y: x*y
>>> d(2,3)
6
>>>
Podemos, de forma limitada, emplear
sentencias condicionales, puesto que
Python nos permite usar la fórmula
if...else como si fuese una expresión:
>>> d = lambda x: ‘Yuju’ U
if x > 2 else ‘ooooh’
>>> d(2)
‘ooooh’
>>> d(3)
‘Yuju’
Pero… ¿Para qué Sirven?Las limitaciones a las funciones lambda
en Python tienen un objetivo bien defi-
nido: evitar el mal uso que se puede
hacer de ellas. Se restringe su uso a
aquellas funciones donde es necesario
pasar operaciones sencillas que el dise-
ñador original de una función no puede
predecir de antemano. Por ejemplo, si
queremos ordenar una lista, la función
de ordenación sorted() intentará compa-
rar los elementos de la misma usando los
métodos que existen por defecto. Pero
¿qué ocurre si los datos a ordenar son
algo especiales? Imaginemos que tene-
mos una lista de datos donde cada ele-
mento es un tupla con el nombre y la
edad de una serie de personas:
>>> l = [(‘Luis’, 65), U
(‘Juan’,28),U
(‘Montse’, 33)]
¿Cómo podemos indicar a sorted que
queremos ordenar la lista por edades? El
diseñador de sorted no puede predecir
todas las posibles estructuras de datos
que se pasarán a la función. Existen tres
opciones, u obligamos a la
persona que pasa los datos
a encapsularlos en objetos
con un método que nos
permita comparar dos
objetos:
01 from functools import
total_ordering
02
03 @total_ordering
04 class Edad(object):
05 def __init__(self,
nombre,edad):
06 self.nombre = nombre
07 self.edad = edad
08
09 def __eq__(self, otro):
10 return cmp(self.edad,
otro.edad)
11
12 def __lt__(self, otro):
13 return self.edad <
otro.edad
14
15 def __str__(self):
16 return self.__repr__
17
18 def __repr__(self):
19 return u”({0} tiene {1})”
.format(self.nombre,
self.edad)
20
21 l = [Edad(‘Luis’, 65),
22 Edad(‘Juan’,28),
23 Edad(‘Montse’, 33),]
24
25 print sorted(l)
O bien definimos una función que nos
permita extraer el valor a comparar de
los objetos:
>>> def mi_ordenacion (x): U
return x[1]
>>> sorted(l, key = U
mi_ordenacion)
[(‘Juan’,28),(‘Montse’, 33), U
(‘Luis’, 65)]
>>>
O bien podemos emplear una función
lambda para generar los valores a com-
parar:
>>> sorted(l, key = U
lambda x: x[1])
[(‘Juan’,28),(‘Montse’, 33), U
(‘Luis’, 65)]
>>>
La primera opción, más clásica de len-
guajes como Java o C#, y (supuesta-
mente) más limpia, tiene un gran pro-
blema. ¿Qué ocurre si queremos ordenar
los datos de varias maneras diferentes?
El diseñador de sorted sólo empleará una
de ellas. Es un enfoque bastante inflexi-
ble, es preferible que la función que
selecciona el criterio de ordenación sea
externa al objeto a ordenar. Además,
como se puede observar, esta opción es
bastante más compleja.Figura 1: Las variables en Python son nombres.
La segunda opción implica el uso de
una función externa que nos devuelve el
valor a comparar para cada objeto. Indi-
camos a sorted qué función usar para
seleccionar los valores a comparar
mediante el parámetro key. Por último
vemos cómo se haría lo mismo con una
función lambda. Salta a la vista que la
tercera opción es la corta, fácil de leer y
elegante (o lo que es lo mismo, la más
«pythonic»).
El lenguaje de programación Common
Lisp se enfrentó a este mismo problema
cuando se diseñó su sistema de objetos,
y la solución fue la misma: sacar fuera
del objeto y de la función de ordenación
el código que genere los datos a compa-
rar. Python empleó la misma técnica, por
lo que mucha gente ve semejanzas entre
ambos lenguajes de programación.
CierresOtro de los conceptos que puede provo-
car más de un dolor de cabeza es el de
cierre. Lenguajes como Javascript giran
en torno a este concepto, ya que les per-
mite crear algo parecido a objetos, ver
Figura 2. En Python, sin embargo, los
cierres son la base de una de las caracte-
rísticas más usadas del lenguaje última-
mente: los decoradores.
Un cierre es un trozo de código fuente
que depende de variables, algunas de las
cuales han sido «capturadas» junto al
trozo de código y quedan aisladas de
cualquier interferencia externa. En el
caso de Javascript, que no posee objetos
propiamente dichos, se usan cierres para
que una serie de funciones compartan
unas variables que no pueden ser accedi-
das desde fuera y que por tanto están
protegidas.
En Python los cierres no funcionan
exactamente como lo hacen en otros len-
guajes. Siguiendo con las funciones
lambda, vamos a crear un cierre con una
de ellas:
>>> def crea_cierre(num):
... return lambda x=num: x+1
...
>>> cierre = crea_cierre(3)
>>> cierre()
4
>>> 4
Analicemos este código. La función
crea_cierre() acepta un parámetro num y
devuelve una función lambda, por lo que
el valor devuelto puede
almacenarse en una varia-
ble y se comportará como
una función. El secreto
está en pasar a la función
lambda el valor num que
queda definido como valor
por defecto para x. Lo
curioso es que en este caso
Python no nos permite
pasar ningún valor a la
función lambda una vez
definida. Si tratamos de
pasar un valor:
>>> cierre(10)
4
>>>
¡Nos sigue devolviendo 4!
No importa qué valor pase-
mos a la función, el contenido de x ha
quedado completamente cerrado y blo-
queado. Por así decirlo, su valor se ha
vuelto inaccesible. Si quisiéramos poder
cambiar el valor de una variable cerrada,
deberíamos usar una función normal en
lugar de una función lambda, porque
como ya vimos, las funciones lambda no
aceptan asignación de variables.
¿Tiene sentido usar cierres en Python?
El sistema de objetos de Python es muy
sencillo y nada engorroso, por lo que es
muy extraño ver el uso de cierres en
Python, salvo por una excepción: los
decoradores. Un decorador es una fun-
ción que intercepta los parámetros de
otra función, hace algo y devuelve la
función original. Normalmente no se cie-
rran variables en un decorador, pero es
posible hacerlo.
La respuesta de Python a los cierres de
funciones son los objetos callable. En
Python es posible crear un objeto que se
comporte como una función. La función
tendrá acceso a una serie de variables de
instancia que se pasan en el constructor
de la misma:
01 >>> class Saluda(object):
02 ... def __init__(self,
saludo):
03 ... self.saludo =
saludo
04 ... def __call__(self,
nombre):
05 ... print “{0} {1}”
.format(self.saludo, nombre)
06 ...
07 >>> saludo = Saluda(‘Hola’)
08 >>> saludo(‘mundo’)
09 Hola mundo
10 >>> saludo = Saluda(‘Hello’)
11 >>> saludo(‘world’)
12 Hello world
13 >>>
Creamos un objeto tradicional con la
única diferencia de poseer un método
llamado __call__, que será el que se eje-
cute cuando invoquemos la instancia de
la clase como si fuese una función. Pri-
mero debemos generar una instancia a la
que pasamos la variable de se «cerrará» y
posteriormente podemos invocar la ins-
tancia como si fuese una función que
acepta parámetros como cualquier otra
función.
Funciones de Primer OrdenComo ya hemos dicho, las funciones
lambda no son especialmente potentes
en Python – en Ruby lo son más – pero
nos permiten pasar comportamientos a
otras funciones. En teoría de lenguajes
de programación, se llama función de
primer orden a aquella función que
acepta otra función como parámetro,
acepta comportamientos además de
datos, lo que la hace especialmente
potente y flexible.
En Python es absolutamente normal
usar funciones como parámetros:
01 def saluda(x,
f=None):
02 if f:
AVANZADOFunciones Lambda
21PYTHONW W W. L I N U X - M A G A Z I N E . E S
Figura 2: Cómo funciona un cierre en torno a una función.
metros. Primero aplicará la
función a «Luis» y «65»,
después a «Juan» y «28»… y
así indefinidamente. map
no está limitada a dos listas,
podemos emplear tantas lis-
tas como queramos:
>>> map(lambda x:
x.upper(),U
[‘Luis’, ‘Juan’U
, ‘Montse’])
[‘LUIS’, ‘JUAN’, U
‘MONTSE’]
>>> map(lambda x,y,z:U
(x, y*z),U
[‘Luis’, U
‘Juan’,’Montse’],U
[65,28,33], [1,2,3])
[(‘Luis’, 65),U
(‘Juan’, 56),U
(‘Montse’, 99)]
>>>
Aunque podríamos pasar
cualquier función previa-
mente definida, estamos pasando funcio-
nes lambda. Es la manera más sencilla y
rápida de aplicar map, aunque como ya
hemos dicho antes, si necesitamos más
de una operación o el código es complejo
es mejor definir la función.
Este map es el mismo que el del
famoso Map/ Reduce de Google, con la
diferencia de que el de Google se aplica a
cientos o miles de máquinas, que aplican
funciones map muy complejas a gran
cantidad de datos. Pero el concepto es el
mismo.
Si este es el map… ¿dónde está
reduce?
>>> reduce(U
lambda x,y: x if x[1] > y[1] U
else y,U
[(‘Luis’, 65), (‘Juan’, 56),U
(‘Montse’, 19)])
(‘Luis’, 65)
>>>
reduce va a aplicar una función de 2
variables a los elementos de una lista
con el objetivo de acabar devolviendo un
solo elemento. Como su propio nombre
indica, reduce una lista a un elemento.
Aquí estamos buscando el valor más
grande de los presentes en la lista, por lo
que la función reductora compara los
dos parámetros y siempre devuelve el
mayor de ellos. Por lo tanto, map aplica
una función a una gran cantidad de
datos y reduce realiza alguna operación
sobre los datos que los convierte en un
solo valor.
A pesar de toda la fanfarria existente
alrededor de Map/ Reduce, lo cierto es
que en Python se usan poco. De hecho,
reduce dejará de ser una función primi-
tiva del sistema en Python 3 y pasará a
ser una función de la librería functools,
por lo que ha sido degradada a ciuda-
dano de segunda fila.
ConclusiónEstamos tan acostumbrados ya a la pro-
gramación orientada a objetos, que se
nos olvida que Python en sus inicios
tomó prestados gran cantidad de concep-
tos y técnicas de otros modelos de pro-
gramación. Las funciones lambda no
están muy vistas como técnica de pro-
gramación, pero las restricciones a las
que las somete Python las ha domesti-
cado lo suficiente como para que en
lugar de entorpecer nuestro código lo
hagan más ligero y sencillo de compren-
der. ■
03 print f(x)
04 else:
05 print x
06 >>>
07 >>> saluda(“hola”)
08 hola
09 >>> saluda(“hola”,
f=lambda x: x.upper())
10 HOLA
11 >>>
Al fin y al cabo, las variables en Python
no son más que nombres que apuntan a
«cosas», sin importar demasiado qué
son esas cosas. No hay gran misterio en
las funciones de primer orden vistas así.
Es lo que haces con ellas lo que las
vuelve interesantes. Python nos provee
de un conjunto de funciones de primer
orden, heredadas de otros lenguajes de
programación funcionales, que nos per-
miten emplear funciones en sus opera-
ciones.
Map y ReduceCuando las funciones lambda realmente
brillan es cuando se usan en conjunción
con las funciones de filtrado y mapeo.
Con todo el revuelo generado por Goo-
gle y otras empresas en torno a las técni-
cas Map/ Reduce, es interesante recordar
los humildes comienzos de estas técni-
cas.
El filtrado y mapeo son técnicas here-
dadas de la programación funcional, ver
Figura 3. En este estilo de programación
se evita modificar los datos originales, y
en lugar de ello se realizan una serie de
transformaciones sobre los datos para ir
reduciéndolos y operando sobre los
resultados hasta conseguir el resultado
deseado. El código resultante suele ser
conciso, aunque no siempre fácil de leer
y entender. La programación funcional
también hace uso de las funciones de
primer orden que hemos visto antes.
Comencemos por echar un vistazo a la
función map:
>>> map(lambda x,y: (x,y),U
[‘Luis’, ‘Juan’,’Montse’], U
[65,28,33])
[(‘Luis’, 65), (‘Juan’, 28), U
(‘Montse’, 33)]
>>>
map aplica una función a dos listas de
valores, recorriendo ambas listas de
valores y pasando los valores como pará-
AVANZADO Funciones Lambda
22 PYTHON W W W. L I N U X - M A G A Z I N E . E S
[1] Alonzo Church: http:// es. wikipedia.
org/ wiki/ Alonzo_Church
Recursos
Figura 3: Map y Reduce en acción.
No es ni será la última vez que desde
esta sección recordemos que la idea ori-
ginal de Stallman era la de que cada
programa libre estuviese construido
sobre librerías de funciones, de manera
que su código fuese reutilizable por
cualquier otro programa.
Quizás en un programa pequeño no
sea muy útil este tipo de diseño, pero
¿qué pasa con esos monstruos consum-
idores de memoria que rondan por
nues tros discos duros? Nos referimos a
programas o entornos del calibre de
Gnome, KDE, Mozilla u OpenOffice.
Todo el mundo se queja de su tamaño
excesivo, su alto consumo de recursos
y su inexplicable complejidad.
Quizás con este artículo desminta-
mos este mito y hagamos que el lector
mire con nuevos ojos a estos maravi -
llosos programas.
Grandes Sistemas de ComponentesEl diseño de un gran programa puede
llevar años y cientos o miles de progra-
madores. Organizar tal cantidad de per-
sonas supone ya una locura sólo por el
hecho de asegurarse que todos cobren.
Pero vayamos a nuestro mundillo
¿cómo podemos organizarlos para que
el desarrollo no acabe en un fiasco?
Esta es la gran cuestión no resuelta
de la informática, pero, aunque no
hayamos encontrado una solución
fiable, sí se disponen de técnicas que
aumentan la probabilidad de que, al
menos, se cree algún software útil.
Una de estas técnicas consiste en
emplear un sistema de componentes
como base para el desarrollo. Un com-
ponente es una cantidad de software
que ofrece un servicio bien definido y
que es reutilizable. Además debe ser
posible reutilizarlo «de verdad»: desde
cualquier lenguaje y cualquier sitio.
Cualquiera que tenga conocimiento
sobre cómo funcionan los lenguajes de
programación a bajo nivel sabrá que
esto es muy muy complicado. Por ello
se han desarrollado infraestructuras
que nos permiten interactuar con los
componentes de manera indirecta. A
este software se le suele llamar “mid-
dleware” (algo así como “software de
en medio”).
Ejemplos famosos de Middleware son
J2EE, que muchos conocerán, y
CORBA, que a muchos les gustaría no
conocer. Ambos son sistemas enormes
y costosos que relegan al programador
a mera herramienta en manos de inge-
nieros, denominados arquitectos, que
conocen su compleja infraestructura.
Pero los sistemas de componentes
también se emplean en software libre y
han dado buenos resultados. Quizás el
más desconocido es UNO, de Universal
Network Objects, el sistema que emplea
OpenOffice, ver Listado [1], y que SUN
desarrolló para su precursor: StarOffice.
PyUNOUn sistema de componentes con el que
sólo se pueda programar en un
lenguaje no tiene mucha utilidad. Por
eso en OpenOffice se han asegurado de
fomentar la creación de interfaces a
distintos lenguajes de programación.
Podemos acceder a UNO usando
Javascript, Java, Ruby, Perl o Python
(ver Recurso [2]).
PyUNO es el nombre de la interfaz y
podremos encontrarlo sin problemas en
nuestra distribución de Linux. Eviden-
temente, necesitamos también tener
instalado OpenOffice. En este artículo
hemos realizado los programas usando
OpenOffice 2.0, que cambió la interfaz
respecto a la versión 1.0, y la versión
de PyUNO 0.7.0.
Un Ejemplo RápidoVamos a crear el famoso «Hola mundo»
con PyUNO. Para ello primero debemos
arrancar OpenOffice con el siguiente
comando desde el directorio donde esté
instalado:
$> ./sofficeU
"-accept=socket,U
INTEGRACIÓNOpenOffice
23PYTHONW W W. L I N U X - M A G A Z I N E . E S
PyUNO: Explota todo el potencial de OpenOffice
Python no haymás 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 código Python, OpenOffice y la tecnología
UNO de OpenOffice. Por José María Ruíz
INTEGRACIÓN OpenOffice
24 PYTHON W W W. L I N U X - M A G A Z I N E . E S
host=localhost,U
port=2002;urp;"
Al arrancar OpenOffice se arranca su
sistema de componentes. Podemos pen-
sar en este proceso como en el
arranque de un servidor, sólo cuando
esté funcionando podrán los clientes
trabajar con él.
Las opciones que pasamos son para
que se cree un socket y se escuche en
localhost en el puerto 2002. Por defecto
OpenOffice no abre el socket, de ma -
nera que no podrán controlar nuestro
OpenOffice sin nuestro consentimiento.
OpenOffice incorpora de serie varios
intérpretes de lenguajes, entre ellos
uno de Python que ya viene preconfig-
urado para poder hacer uso de la libr-
ería UNO. Está junto al resto de eje-
cutables de OpenOffice, así que lo eje-
cutaremos desde allí. El programa que
usaremos se encuentra en el Listado
[2].
El proceso es el siguiente:
• Obtenemos un contexto local (un
sitio donde guardar los datos de la
conexión)
• Arrancamos el componente UnoUrl-
Resolver que nos sirve para acceder
a otro OpenOffice en otro equipo
(en nuestro caso accederemos a
nuestro propio equipo)
• Emplearemos el objeto resolver para
acceder al OpenOffice remoto
• Arrancamos un «Desktop» (escrito-
rio) de OpenOffice (esto es, una
instancia de OpenOffice vacía)
• Arrancamos un SWriter (es decir, el
procesador de textos) en el escrito-
rio
• Obtenemos un cursor, con el que
podremos posicionarnos dentro del
texto
• e insertamos texto en el cursor
El resultado, no muy espectacular,
podemos verlo en la Figura [1]. Ya te -
nemos nuestro «hola mundo» insertado
en SWriter.
¿Demasiado código? Piensa por un
momento lo que estamos haciendo.
01 import uno
02
03 localContext = uno.getComponentContext()
04 resolver = localContext.ServiceManager.createInstanceWithContext(“com.sun.star.bridge.UnoUrlResolver”, localContext )
05 ctx = resolver.resolve( “uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext” )
06 desktop = ctx.ServiceManager.createInstanceWithContext(“com.sun.star.frame.Desktop”,ctx)
07 doc =desktop.loadComponentFromURL(“private:factory/swriter”,”_blank”,0,())
08 cursor = doc.Text.createTextCursor()
09 doc.Text.insertString( cursor, “Hola Mundo”, 0 )
10 ctx.ServiceManager
Listado 1: Programa «Hola Mundo»
01 import uno
02 import random
03 import time
04 import httplib
05 import csv
06
07 class Calc:
08 def __init__(self):
09 self.conecta()
10
11 def conecta (self):
12 self.local = uno.getComponentContext()
13 self.resolver = self.local.ServiceManager.createInstanceWithContext(“com.sun.star.bridge.UnoUrlResolver”, self.local)
14 self.context =self.resolver.resolve(“uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext”)
15 self.desktop = self.context.ServiceManager.createInstanceWithContext(“com.sun.star.frame.Desktop”, self.context)
16 #self.doc = self.desktop.getCurrentComponent()
17 self.doc = self.desktop.loadComponentFromURL(“private:factory/scalc”,”_blank”,0,())
18 self.hojas = self.doc.getSheets()
19 self.s1 = self.hojas.getByIndex(0)
20
21 def actualiza(self, cotizacion, fila):
22
23 i = 0
24 for entrada in cotizacion:
25 if (i == 0) or (i == 2) or (i ==3):
26 self.s1.getCellByPosition(i,fila).
setString(entrada)
27 else:
28 self.s1.getCellByPosition(i,fila).setValue(float(entrada))
29
30 i = i + 1
31
32 def getSimbolo(simbolo):
33 c = httplib.HTTPConnection(“finance.yahoo.com”)
34 c.request(“GET”,”/d/quotes.csv?s=”+simbolo+”&f=sl1d1t1c1ohgv&e=.csv”)
35 r = c.getresponse()
36 cad = r.read()
37 reader = csv.reader([cad])
38 resultado = []
39 for row in reader:
40 resultado = row
41 return resultado
42
43 if __name__ == ‘__main__’:
44
45 simbolos = [“GOOG”,”MSFT”,”RHAT”]
46
47 c = Calc()
48
49 while(1):
50 i = 0;
51 for s in simbolos:
52 c.actualiza(getSimbolo(s),i)
53 i = i + 1
54
55 time.sleep(10)
Listado 2: «OfficeBroker»
Hemos levantado dos componentes y
hecho acceso remoto a otro OpenOf-
fice. Este segundo OpenOffice puede
estar en una máquina al otro lado del
mundo. Es algo bastante impresionan -
te, pero por el momento poco útil.
Veamos un poco más sobre UNO antes
de realizar un programa más útil.
Arquitectura de UNOOpenOffice está implementado en
C++. UNO se usa internamente para
realizar cualquier cosa. Básicamente,
OpenOffice no es más que una gran
cantidad de componentes que interac-
túan entre sí. Todo dentro de OpenOf-
fice es un componente, así que
podemos acceder a cualquier parte de
la aplicación, ¡incluso reconstruir
Open Office en Python!
Los sistemas de componentes usan
un registro de componentes al que se le
puede pedir que arranque compo-
nentes. El registro localiza el compo-
nente en disco y lo carga en memoria,
de manera que puede ser usado. Las
llamadas a las funciones no se realizan
directamente, sino que se suele
emplear algún sistema no dependiente
de lenguaje o plataforma, como puede
ser XML o un formato ASCII.
El registro también debe ser capaz de
gestionar los recursos que consume el
componente, descargándolo de memo-
ria cuando ya no sea necesario.
Los componentes pueden ser progra-
mados en cualquier lenguaje con el que
se tenga interfaz. Un componente es un
conjunto de ficheros que proporcionan
un servicio. Se acompañan de un
fichero XML que describe su funcional-
idad. Lo mejor es que podemos vincu-
lar ese servicio a algún componente
gráfico, como por ejemplo un botón o
menú.
Comenzaremos por realizar un pro-
grama que funcionará de manera
externa a OpenOffice, y después creare-
mos un componente con él y lo inte-
graremos en OpenOffice.
Nuestro Programa de StocksComencemos con la parte útil, ver Lis-
tado [2]. Vamos a crear un sistema que
nos permita controlar las acciones de
una serie de empresas que están en
bolsa dentro de un índice tecnológico,
el Nasdaq (para algo estamos en una
revista de informática), usando la hoja
de cálculo SCalc y un programa Python.
Nuestro programa accederá usando
Internet a un sitio web donde podrá
recoger los datos en CSV (Valores Sepa-
rados por Comas), los procesará y los
introducirá en SCalc.
Comenzaremos por crear una fun-
ción que recoja el fichero CSV y lo pro-
cese. Lo que hacemos es conectarnos
con la página web finance.yahoo.com.
Yahoo tiene un sitio web bastante
avanzado sobre cotizaciones de bolsa,
y uno de sus servicios nos permite
recoger los datos de cotización de una
empresa en tiempo real en formato
CSV. Sin embargo, Yahoo no nos per-
mitirá acceder a los datos demasiado a
menudo, ya que dispone de un servi-
cio de pago para ello, así que puede
cortarnos el «grifo» en cualquier
momento si hacemos demasiadas con-
sultas por minuto. Por eso recogere-
mos los datos cada 10 segundos. La
función getSimbolo() se encargará de
ello.
Ahora ya tenemos los datos, te nemos
que mandarlos a SCalc. Hemos creado
un objeto llamado Calc para gestionar
el acceso. Hemos metido en el método
constructor (__init__) el código que
conecta con OpenOffice, puesto que
sólo lo haremos una vez. Una hoja de
cálculo posee varias «hojas», así que
tendremos que solicitar una usando el
método getSheets(), que nos devuelve
una lista con las distintas hojas. Dentro
de esta lista usaremos getByIndex()
para seleccionar la primera hoja, que
es la que se ve cuando arrancamos
SCalc.
El método actualiza() admite una
lista con los datos de cotización y
número que representa la fila donde
aparecerá en SCalc. Una hoja de cálculo
se compone de celdas, y éstas tienen un
tipo. La función getCellByPosition() nos
permite acceder a una celda pasándole
la columna y la fila (al revés de lo nor-
mal, así que cuidado).
Una vez localizada la celda, tenemos
varias funciones para poder asignar un
valor:
• setString(): para poner una cadena
• setValue(): para poner un número
• setFormula(): para poner una fór-
mula
El dato cotización es la lista de
parámetros de cotización, pero vienen
dados como cadenas de caracteres. Las
posiciones 0, 2 y 3 son realmente cade-
nas, pero el resto son números. Por eso
tenemos que convertir ciertos valores
al tipo float() mediante la función
float().
El resultado se puede ver en la Figura
[2]. Observamos cómo se abre una ven-
tana de SCalc y se rellena con los val-
INTEGRACIÓNOpenOffice
25PYTHONW W W. L I N U X - M A G A Z I N E . E S
Figura 1: Un documento de Write de OpenOffice con el ineludible
“Hello World” generado a partir de PyUNO.
Figura 2: El programa examina los valores de la bolsa NASDAQ de
Yahoo a intervalos regulares y los inserta en una hoja de cálculo.
paquetes que OpenOffice admite tienen
una estructura fija. Son ficheros ZIP
que contienen los ficheros con el
código fuente, recursos (como imá-
genes) y un fichero de configuración
XML.
Los ficheros deben tener nombres
especiales. El fichero de configuración
debe llamarse Addons.xcu y permite
asignar el código fuente del paquete
con el widget que deseemos, un botón,
una entrada de un menú… Ver Listado
[3].
La sintaxis del fichero parece bas-
tante complicada, cuando en realidad
no es muy difícil de entender. Básica-
mente decimos que queremos que nue-
stro componente se asocie con una
entrada en el menú «Addons» que está
en Tools o Herramientas en castellano.
Nuestro componente tiene una ruta que
especificaremos después y que es:
ores de las contizaciones, además de
cómo se actualizan cada 10 segundos.
Si creamos un gráfico que use esos va -
lores, se actualizará con ellos.
Pero este es un programa externo…
estaría bien que pudiésemos hacer eso
pulsando un botón
Creamos un Componente UNOLos componentes UNO no son más que
código debidamente empaquetado. Los
INTEGRACIÓN OpenOffice
26 PYTHON W W W. L I N U X - M A G A Z I N E . E S
01 import uno
02 import unohelper
03
04 import random
05 import time
06 import httplib
07 import csv
08
09 from com.sun.star.task import XJobExecutor
10
11 def getSimbolo(simbolo):
12 c = httplib.HTTPConnection (“finance.yahoo.com”)
13 c.request(“GET”,”/d/quotes.csv?s=”+simbolo+”&f=sl1d1t1c1ohgv&e=.csv”)
14 r = c.getresponse()
15 cad = r.read()
16 reader = csv.reader([cad])
17 resultado = []
18 for row in reader:
19 resultado = row
20 return resultado
21
22 class StockJob( unohelper.Base, XJobExecutor ):
23 def __init__( self, ctx ):
24
25 self.ctx = ctx
26
27 def trigger( self, args ):
28 desktop =self.ctx.ServiceManager.createInstanceWithContext(“com.sun.star.frame.Desktop”, self.ctx )
29
30 model = desktop.getCurrentComponent()
31
32 self.hojas = model.getSheets()
33
34 self.s1 = self.hojas.getByIndex(0)
35
36 simbolos = [“GOOG”,”MSFT”,”RHAT”]
37 i = 0;
38
39 for s in simbolos:
40 self.actualiza(getSimbolo(s),i)
41 i = i + 1
42
43 def actualiza(self, cotizacion, fila):
44 i = 0
45 for entrada in cotizacion:
46 if (i == 0) or (i == 2) or (i ==3):
47 self.s1.getCellByPosition(i,fila).setString(entrada)
48 else:
49 self.s1.getCellByPosition(i,fila).setValue(float(entrada))
50
51 i = i + 1
52
53 g_ImplementationHelper =unohelper.ImplementationHelper()
54
55 g_ImplementationHelper.addImplementation( StockJob,“org.openoffice.comp.pyuno.linuxmagazine.Stock”,(“com.sun.star.task.Job”,),)
Listado 4: stock_comp.py
01 <?xml version=”1.0” encoding=”UTF-8”?>
02 <oor:nodexmlns:oor=”http://openoffice.org/2001/registry”
03 xmlns:xs=”http://www.w3.org/2001/XMLSchema”
04 oor:name=”Addons”oor:package=”org.openoffice.Office”>
05 <node oor:name=”AddonUI”>
06
07 <node oor:name=”AddonMenu”>
08 <node oor:name=”org.openoffice.comp.pyuno.linuxmagazine.Stock”oor:op=”replace”>
09 <prop oor:name=”URL” oor:type=”xs:string”>
10 <value>service:org.openoffice.comp.pyuno.linuxmagazine.Stock?insert</value>
11 </prop>
12 <prop oor:name=”Title” oor:type=”xs:string”>
13 <value/>
14 <value xml:lang=”en-US”>Stock Market</value>
15 <value xml:lang=”es”>Cotización en Bolsa</value>
16 </prop>
17 <prop oor:name=”Target” oor:type=”xs:string”>
18 <value>_self</value>
19 </prop>
20 <prop oor:name=”ImageIdentifier”oor:type=”xs:string”>
21 <value>private:image/3216</value>
22 </prop>
23
24 </node>
25 </node>
26 </node>
27 </oor:node>
Listado 3: Addons.xcu
org.openoffice.comp.pyuno.U
linuxmagazine.Stock
Esta ruta la hemos creado nosotros y
tenemos que tener cuidado de que sea
única, por eso hemos incorporado li -
nuxmagazine en ella ;). Definimos un
título, que puede estar en varios
idiomas, y una imagen, que hemos
escogido de entre las que proporciona
OpenOffice.
El fichero con el código fuente Python
en sí se puede ver en el Listado 4. Ten-
emos un objeto llamado StockJob que
será el que se invocará en caso de pul-
sar la entrada en el menú. Ese objeto se
vincula a la ruta que vimos antes. Cada
vez que se pulse sobre la entrada del
menú se ejecutará el método trigger,
que descargará de Internet las cotiza-
ciones y las mostrará en la hoja de cál-
culo. Es posible hacer que sólo se
muestre el menú cuando arrancamos la
hoja de cálculo SCalc, pero por motivos
de espacio no hemos puesto la restric-
ción. Aún así, si no estamos en una
hoja de cálculo no suce derá nada, sim-
plemente no funcionará.
Manejo de Paquetes en OpenOfficeAhora tenemos que generar nuestro
paquete UNO. Para ello necesitaremos
el programa ZIP, gzip no nos vale, y
crear un fichero:
$> zip stock.zipU
stock_comp.py Addons.xcu
updating: ...../Addons.xcuU
(deflated 59%)
updating: ...../stock_comp.pyU
(deflated 57%)
>
Este fichero debe ser integrado en
Open Office, iremos al directorio donde
esté instalado y ejecutaremos como
root:
$> sudo ./unopkg addU
stock.zip
>
Con esto concluye la instalación del
paquete…¡no ha sido tan difícil!
Cuando arranquemos de nuevo
OpenOffice podremos seleccionar la
hoja de cálculo SCalc, y en el menú
Tools/ Herramientas, veremos cómo ha
aparecido al final un nuevo submenú:
«Complementos (add-ons)». Dentro
del mismo aparecerá una nueva
entrada llamada «Cotización de
Bolsa». Si la pulsamos aparecen los
datos de 3 compañías (Google,
Microsoft y Redhat) del Nasdaq en
nuestra hoja de cálculo.
ConclusiónPython nos permite un uso nuevo de
algo tan trillado como puede ser un
paquete ofimático. OpenOffice entero
es accesible desde Python; no es difícil
imaginarse programas que podrían
facilitarnos mucho la vida y no son tan
difíciles de crear gracias a PyUNO. No
hemos explorado la posibilidad de
actuar sobre un OpenOffice remoto por
falta de espacio, pero es una nueva
opción que abre un camino para aplica-
ciones muy interesantes, como puede
ser la edición distribuida de documen-
tos o un uso más creativo de la hoja de
cálculo.
Todo un mundo de posibilidades se
abre ante nosotros gracias a Python. ■
INTEGRACIÓNOpenOffice
INTEGRACIÓN Java
28 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Muchos os preguntaréis qué es Jython.Bien, empecemos desde el principio. Ungrupo de programadores, Jim Hugunim(su creador) y Guido van Rossum (perso-nalidad dentro del mundo de Python)entre otros, decidieron que la simpleza yla limpieza del código de Python haríaque programar en Java fuera perfecto,así que se pusieron manos a la obra ycrearon Jython. Jython es la implemen-tación de Python en la plataforma Java,combinando la potencia de los paquetesde Java, como JavaBeans, con la facili-dad y rapidez de desarrollo de aplicacio-nes de Python. Recordad que desarrollaruna aplicación es por lo menos dos vecesmás corto en Python que en Java.
Jython posee las mismas característi-cas de Python y además posee la carac-terística de poder trabajar con las libre -rías de Java, de forma que, por ejemplo,podemos disponer del bonito swing deJava o utilizar JavaBeans e incluso pro-gramar applets. En la Web de Jython en[1] aparecen algunos ejemplos deapplets desarrolla dos en Jython. Estohace de él un lenguaje muy potente, yaque nos ahorra mos tiempo, líneas decódigo y resulta menos engorroso de leerque Java. Incluso podemos compilar elcódigo para no tener la necesidad deinstalar Jython antes de ejecutar nuestraaplicación en cualquier ordenador, ya
que es capaz de generar Bytecode Java(aunque sí necesitamos tener Java,obvia mente). Aunque ciertamente notodo es positivo. Las aplicaciones desa -rrolladas con Jython suelen ser bastantemás lentas que las desarro lladas conJava, algo de lo que se quejan muchosprogramadores, pero aún así, la potenciay rapidez de los ordenadores de hoy haceque apenas se note la diferencia.
Muchos os preguntareis si hace faltasaber Java para programar Jython. Enprincipio, no. Sí es conveniente tener
idea de su funcionamiento y disponer dela API de Java, disponible en la web deSun [2], para saber qué queremos hacery cómo lo queremos hacer, pero la sin-taxis y la forma de programar es muydiferente. Programar en Java sin la APIde Sun resulta, en la mayor parte de loscasos, imposible. En cambio, para pro-gramar en Jython sí es necesario saberprogramar en Python, así pues, damospor sabido todo lo que hayáis aprendidoen los artículos anteriores de estarevista.
Yvonne L
ess - 1
23R
F.com
01 import javax.swing as swing
02 import java.awt as awt
03
04 cuadroTexto =swing.JTextField(10)
05
06 def __init__():
07 win = swing.JFrame(“Ejemplo conbotones”)
08 acciones = [‘uno’, ‘dos’,‘tres’]
09
10 pnlBotones =swing.JPanel(awt.FlowLayout())
11
12 pnlBotones.add(cuadroTexto)
13 for cadaBoton in acciones:
14 pnlBotones.add (swing.JBut-ton(cadaBoton,actionPerformed=accion))
15
16 win.contentPane.add(pnl-Botones)
17 win.size=(300,300)
18 win.pack()
19 win.show()
20
21 def accion(event):
22 accionBoton = event.getAction-Command()
23 if accionBoton == “uno”:
24 cuadroTexto.setText(“UNO”)
25 elif accionBoton == “dos”:
26 cuadroTexto.setText(“DOS”)
27 else:
28 cuadroTexto.setText(“TRES”)
29 root = __init__()
Listado 1: Tres Botones
No has encontrado tu lector RSS? Hazte uno tú mismo con Jython.
Cuando los Mundos ChocanOs descubrimos Jython, la forma mas sencilla de desarrollar vuestras aplicaciones Java como si las progra-
márais con Python. Por Pedro Orantes y José María Ruíz
En este artículo aprenderemos a usarelementos básicos de Java en Jython,trabajaremos con swing, y usaremosalgunos objetos de Java como son los
vectores, entre otras cosas. Para el ejem-plo de este artículo nos valdremos deuna aplicación externa ligeramentemodi ficada (el código original lo encon -traréis en la web de xml.com en [3])desarrollada en Python, para que podáisecharle un ojo al código si os apetece,desde la cual parsearemos el xml de undocumento RSS para leer las últimasnoticias de los blogs que más frecuente-mente visitemos. Más adelante expli-caremos esto detalladamente.
Instalación de JythonPara trabajar con Jython, necesitamostener Java instalado en nuestro orde-
nador. Podemos usar el Java Runtime
Edition (j2re) o el Java Developers
Kit(j2sdk), en su version 1.4.2 comomínimo, descargables desde [2]. Ademásnecesitamos instalar el intérprete deJython disponible en [1], en su últimaversión estable (la jython-2.1) y, porúltimo, para ejecutar nuestra aplicación,deberemos tener instalado el intérpretede Python (versión 2.3).
Una vez hemos descargado el intér-prete de Jython, debemos proceder a lainstalación del mismo. Ejecutamos java
jython-21 y nos saldrá el instalador(Figura 1), que nos pedirá confirmar unaserie de opciones y un directorio, y ya
INTEGRACIÓNJava
29PYTHONW W W. L I N U X - M A G A Z I N E . E S W W W . L I N U X - M A G A Z I N E . E S
Figura 1: Nuestra utilidad busca-palabras en marcha.
001 #!/usr/bin/jython
002
003 import javax.swing as swing
004 import java.lang as lang
005 import java.awt as awt
006 import java.util as util
007 import os
008
009 class Lector:
010 def exit(self, event):
011 lang.System.exit(0)
012
013 def __init__(self):
014
015 self.vectorrss = util.Vector()
016 self.vectorurl = util.Vector()
017 self.listaRSS()
018 self.listaNoticias()
019 self.pnlBotones()
020 self.menu()
021 if os.path.exists(‘listarss.txt’):
022 self.leeFicheroRss()
023 self.win = swing.JFrame(“JyRss”, size=(300,300),windowClosing=self.exit)
024 self.win.setJMenuBar(self.menu)
025 self.win.contentPane.add(self.pnlBoton,awt.Bor-derLayout.NORTH)
026 self.win.contentPane.add(self.jscplista, awt.Bor-derLayout.WEST)
027 self.win.contentPane.add(self.jscpNoticias,awt.BorderLayout.CENTER)
028 self.win.setSize(600, 400)
029 self.win.show()
030
031 def pnlBotones(self):
032 self.pnlBoton = swing.JPanel(awt.FlowLayout())
033 acciones = [“Agregar”,”Borrar”,”Leer”]
034 self.txtUrl = swing.JTextField(10)
035 lblNombre = swing.JLabel(“Nombre”)
036 self.txtNombre = swing.JTextField(10)
037 lblUrl = swing.JLabel(“Url”)
038 self.pnlBoton.add(lblNombre)
039 self.pnlBoton.add(self.txtNombre)
040 self.pnlBoton.add(lblUrl)
041 self.pnlBoton.add(self.txtUrl)
042
043 for cadaBoton in acciones:
044 self.pnlBoton.add(swing.JButton(cadaBoton,actionPerformed=self.accionMenu))
045
046 def menu(self):
047 opciones = [“Guardar”]
048 self.menu = swing.JMenuBar()
049 archivo = swing.JMenu(“Archivo”)
050 for eachOpcion in opciones:
051 archivo.add(swing.JMenuItem(eachOpcion, action-Performed=self.accionMenu))
052 self.menu.add(archivo)
053
054 def listaRSS(self):
055 self.lstLista = swing.JList()
056 self.jscplista = swing.JScrollPane(self.lstLista)
057 self.jscplista.setSize(100,100)
058
059 def listaNoticias(self):
060 self.lstNoticias = swing.JEditorPane()
061 self.jscpNoticias = swing.JScrollPane(self.lst-Noticias)
062
063 def leeFicheroRss(self):
064 f = open(‘listarss.txt’,’r’)
065 fu = open(‘listaurl.txt’, ‘r’)
066 linea = f.readline()
067 lurl = fu.readline()
068 while linea:
069 self.vectorrss.add(linea)
070 self.vectorurl.add(lurl)
071 linea = f.readline()
072 lurl = fu.readline()
073 f.close()
074 fu.close()
075 self.lstLista.setListData(self.vectorrss)
076
077 def leeFicheroNoticias(self):
078 fg = open(‘news.txt’,’r’)
079 texto = fg.read()
080 fg.close()
081 self.lstNoticias.setText(texto)
082
083 def guardarFichero(self):
Listado 2: JyRSS.py
INTEGRACIÓN Java
30 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Type "copyright", "credits" orU
"license" for more information.
>>> print 'Hola Mundo'
Hola Mundo
>>>
Bien, vamos a empezar a ver algunosejemplitos en java. En primer lugar, ¿quétal el típico Hola mundo con swing? Esteejemplo mostrará una ventana llamadaHola Mundo, con un cuadro de texto.Para cerrarla tendréis que cerrar el intér-prete con Control + C, ya que en elejemplo no implementamos la salida dela aplicación.
01 $ jython
02 Jython 2.1 on java1.4.2_05
(JIT: null)
03 Type "copyright", "credits"
or "license"
for more information.
04 >>> import javax.swing as
swing
05 >>> win = swing.JFrame("Hola
mundo")
06 >>> texto =
swing.JLabel("Hola
mundo")
07 >>>
win.contentPane.add(texto)
08 >>> win.pack()
09 >>> win.show()
10 >>>
Como podéis ver resulta muy sencillo, ala vez de que swing resulta muy agra -dable para la vista. Java incluye tambiénun sistema para cambiar la apariencia dela interfaz por si no os gusta la quearranca por defecto.
ProgramaciónAl igual que en Python, para nuestrasaplicaciones lo mas cómodo es hacerque el intérprete ejecute un fichero (ovarios) de código. Jython no tieneningún tipo de extensión establecidapara los ficheros, y lo más normal esusar la misma que Python .py para tenernuestros ficheros de código bien diferen-ciados de los demás.
Para usar las librerías de Java, loprimero que tenemos que hacer esimportarlas al estilo de siempre dePython. Como hemos visto en el ejemplode antes, para importar las librerías deswing basta con importar lo que nece-sitemos. Imaginad que necesitamos laclase Vector, que está dentro del paquetede utilidades, bien podemos hacerimport java.util as util o bien directa-mente import java.util.vector as vector, yya tendremos acceso a esa clase. Parautilizarla bastaría con llamar a su cons -
tructor (os recuerdo que tengáis siemprea mano la documentación del API deJava) con él, crearemos una ocurrenciavector (en este caso), pepe = util.vec-
tor() y una vez creada, podemos accedera cualquiera de sus métodos directa-mente en la ocurrencia que acabamos decrear, ‘pepe.add(“Hola”)‘ que, para losque no estén muy familiarizados conJava, añade un objeto de tipo String
(cadena de caracteres) al vector.Cada ocurrencia de Java tiene el
mismo funcionamiento en Jython. Esimportante que tengáis esto en cuenta.
tendremos Jython instalado en nuestroordenador. Adicionalmente podemosenlazar los ejecutables de Jython a nues -tro directorio de binarios del sistema(ln -s jython-2.1/jython /usr/bin/jython yln -s jython-2.1/jythonc /usr/bin/jythonc
en nuestro caso) para no tener que eje-cutar los binarios desde el directoriodonde lo tengamos instalado. Esto últi-mo es recomendable, además de queresulta mucho más cómodo.
Primeros PasosBueno, ya está todo preparado en nues -tro sistema. Es hora de ver cómo fun-ciona Jython. Para empezar, podéistrastear un poco con el intérprete comolo habéis hecho con el de Python, y asípodréis ver que el funcionamiento esidéntico.
$ jython
Jython 2.1 on java1.4.2_05U
(JIT: null)
Figura 2: Ejemplo con botones Swing.
Figura 3: Buscador desde la línea de comandos.
084 fg = open(‘listarss.txt’,’w’)
085 furl =open(‘listaurl.txt’,’w’)
086 j = self.vectorrss.size()
087 i = 0
088 while i<=j-1:
089 texto = self.vectorrss.get(i)
090 fg.write(texto +’\n’)
091 texto = self.vectorurl.get(i)
092 furl.write(texto +’\n’)
093 i = i+1
094 fg.close()
095 furl.close()
096
097 def accionMenu(self, event):
098 self.accion = event.getAction-Command()
099 if self.accion == ‘Agregar’:
100 if self.txtNombre.getText()== “”:
101 self.vectorrss.add(“SIN NOM-BRE\n”)
102 else:
103 self.vectorrss.add(self.txtNombre.getText())
104 if self.txtUrl.getText() ==“”:
105 self.vectorurl.add(“SINURL\n”)
106 else:
107 self.vectorurl.add(self.txtUrl.getText())
108
109 self.lstLista.setListData(self.vectorrss)
110 self.txtNombre.setText(“”)
111 self.txtUrl.setText(“”)
112
113 elif self.accion == ‘Leer’:
114 item = self.lstLista.getSe-lectedIndex()
115 url =self.vectorurl.get(item)
116 os.system(‘python lrss.py ‘+url)
117 self.leeFicheroNoticias()
118
119 elif self.accion == ‘Borrar’:
120 itemborrar =self.lstLista.getSelectedIn-dex()
121 self.vectorrss.remove(item-borrar)
122 self.vectorurl.remove(item-borrar)
123 self.lstLista.setListData(self.vectorrss)
124
125 elif self.accion == ‘Guardar’:
126 self.guardarFichero()
127
128 root = Lector()
Listado 2: JyRSS.py (Cont.)
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). Paraello voy a crear tres botones, a cada uno lo llamaremos deuna forma distinta y les añadiremos un listener (para cogerlos eventos que produce cada botón), de forma que al pul-sar en cada uno, se escriba su nombre en un cuadro detexto. Veamos cómo se hace esto en el Listado 1.
Es sencillo ¿verdad?, si os dais cuenta, para crear losbotones he usado código propio de Python usando una listacon los nombres de los botones y creándolos con un buclefor, dentro del cual llamo al constructor de JButton, pasán-dole unos argumentos, y añadiéndolos en el panel medianteel método .add() que implementa JPanel. Bueno, esperoque os haya gustado el ejemplo, porque ahora viene nuestraaplicación en serio.
Lector de Noticias RSS -JyRSS-Puede que algunos de vosotros os preguntéis qué es RSS.RSS no es más que un fichero .xml con una serie de etique-tas. Éstas siguen un estándar definido por xml.com paratransmitir pequeñas noticias, por ejemplo, a través deInternet, de forma que no haga falta abrir un navegadorpara leerlas, sino que sólo bastaría con usar una aplicaciónque descargue y prepare la noticia para que la podamosleer, como hace nuestra aplicación JyRSS.
Para empezar, necesitamos el código que hemos men-cionado al principio del artículo. Este código será el encar-gado de descargar y parsear el fichero RSS con las noticias,de forma que guardará en un fichero el contenido yaparseado del documento RSS para que nuestra aplicaciónpueda cargar su contenido dentro de un cuadro de texto.Tiene algunas limitaciones, y es que no soporta todas lascodificaciones de texto, por lo que os recomiendo que cuan-do lo probéis, uséis direcciones extranjeras, a ser posible eninglés, como Slashdot.com (http:// www. slashdot. com/
index. rss), ya que con algunos blogs en castellano, comobarrapunto.com, no funciona correctamente debido a unaserie de caracteres contenidos en el documento.
JyRSS constará de varias partes. Un JFrame (ver Listado1) donde irán embebidos el Panel de botones y los cuadros
de texto. Uno de los cuadros de texto es un JList, dondemostraremos los nombres de los sitios que contienen lasnoticias, y el otro un JEditorPane, donde se mostrarán lasnoticias. Además ambos hacen uso de JScrollPane para quepuedan hacer scroll en caso de que haya texto que ocupetoda la pantalla.
También hacemos uso del JMenu y de JMenuBar paracrear el pequeño menú archivo, donde está la opción deguardar, que guardará en dos ficheros (uno con los nom-
bres de los sitios y otro con las urls de cada sitio) todos lossitios web que le hayamos añadido. Este menú no es nadanecesario, pero queríamos incluirlo para que viérais lo fácilque es crearlo.
El panel de botones tendrá tres JButtons, uno para añadir
una nueva url a la lista, uno para borrar una url de la lista yotro que lanzará el pequeño par ser de RSS (lrss.py) y nosmostrará las noticias de cada sitio que aparecerán en elJEditorPane.
También se hace uso de la clase Vector. Usaremos dosvectores, uno para guardar los nombres y otro para las
Pongo los nombres de las clases ypaquetes de Java que se van a usar, paraque os vayan sonando cuando vayáisleyendo el código. Así podréis buscarmás rápido en la API de Java y no ten-dréis problemas para encontrarlas.Vuelvo a hacer hincapié en que es nece-sario que lo tengáis.
Mejoras para JyRSSEs obvio que a este código le faltanmuchas cosas, además de que debe tenervarios bugs, como por ejemplo que alpulsar el botón Leer cuando no hayninguna url y/ o ningún nombre, Javalanza una excepción. Os animamos a quelo depuréis.
Os sugerimos que le hagáis algunasmejoras, ya que de esta manera osservirá para practicar con el código queos dejamos. Por ejemplo, hacer que elcuadro JEditorPane os cambie la aparien-cia del texto (negrita, cursiva, etc…),que cada vez que pulséis sobre un nom-bre en la JList lea la noticia directamente(deberéis trabajar con el listener deJList), añadir la opción de guardar lasnoticias del sitio que más os gusten,etc…
Entornos de Programación JythonMucha gente prefiere trabajar con IDE’s
(interfaz para el desarrollo de aplica-
ciones) a la hora de programar.Actualmente no he encontrado aúnninguno que sea exclusivamente paradesarrollar en Jython. Lo que sí existe,son plugins que instalamos en otrosIDE’s y que nos permiten trabajar coneste lenguaje. Podemos encontrar dife -rentes plugins para dos de los IDE’s máspopulares, uno para Netbeans (ver [4]),que os podéis bajar desde la aplicaciónde actualización que lleva implementa-da. Y luego tenéis otro para Eclipse (ver[5]) llamado Red Robin, que podéisencontrar en [6]. Tanto en la web deNetbeans como en la web de Red
Robin, se explica cómo debemos insta-larlos. ■
direcciones. El vector de nombres se lopasaremos a la ocurrencia JList paraque añada todos los nombres de lossitios en pantalla. Usaremos el paquetejava.lang para implementar la funciónde salir de la ventana al pulsar la x denuestra aplicación (no se implementapor defecto).
De Python, haremos uso del paqueteos, desde el que nos valdremos deos.path.exists() para comprobar si existeel fichero de nombres al arrancar la apli-cación y de os.system(), que ejecutará elscript Python que leerá las noticias. Ésteguardará las noticias en un fichero, queluego leerá nuestra aplicación. No ospreocupéis si veis que tarda un poco, esoes por dos motivos, tiene que descargarel fichero de Internet, y además tieneque parsearlo, y las librerías que utiliza(minidom) son bastante lentas.Usaremos también las utilidades deescritura y lectura de ficheros (open(),write(), read(), etc…) para manejarnoscon ellos. Podemos ver el resultado en laFigura 3.
Como véis, programar java conJython no resulta para nada difícil, alcontrario, resulta muy cómodo, y desdeluego mucho mas sencillo que Java. Enel Listado 3 encontraréis el códigoPython de la pequeña aplicación exter-na.
INTEGRACIÓN Java
32 PYTHON W W W. L I N U X - M A G A Z I N E . E S
[1] Página de Jython:
http:// www. jython. org
[2] Descarga de Java:
http:// java. sun. com
[3] Código original del programa:
http:// www. xml. com/ lpt/ a/ 2002/ 12/ 18/
dive-into-xml. html
[4] Plugins de Netbean:
http:// www. netbeans. org
[5] IDE Eclipse:
http:// www. eclipse. org
[6] Plugin Jython para Eclipse:
http:// home. tiscali. be/ redrobin/ jython/
Recursos
01 from xml.dom import minidom
02 import urllib
03
04 DEFAULT_NAMESPACES = \
05 (None, # RSS 0.91, 0.92, 0.93, 0.94, 2.0
06 ‘http://purl.org/rss/1.0/’, # RSS 1.0
07 ‘http://my.netscape.com/rdf/simple/0.9/’ # RSS 0.90
08 )
09 DUBLIN_CORE = (‘http://purl.org/dc/elements/1.1/’,)
10
11 def load(rssURL):
12 return minidom.parse(urllib.urlopen(rssURL))
13
14 def getElementsByTagName(node, tagName, possible-Namespaces=DEFAULT_NAMESPACES):
15 for namespace in possibleNamespaces:
16 children = node.getElementsByTagNameNS(namespace,tagName)
17 if len(children): return children
18 return []
19
20 def first(node, tagName,possibleNamespaces=DEFAULT_NAMESPACES):
21 children = getElementsByTagName(node, tagName, pos-sibleNamespaces)
22 return len(children) and children[0] or None
23
24 def textOf(node):
25 return node and “”.join([child.data for child innode.childNodes]) or “”
26
27 if __name__ == ‘__main__’:
28 import sys
29 rssDocument = load(sys.argv[1])
30 fn = open(‘news.txt’,’w’)
31 Noticia=”“
32 for item in getElementsByTagName(rssDocument,‘item’):
33 Noticia = ‘Title: __’ + textOf(first(item,‘title’))+ “__\n”
34 Noticia = Noticia + ‘Link: \n ‘+ textOf(first(item,‘link’))+ “\n”
35 Noticia = Noticia + ‘Description: \n\n ‘ +textOf(first(item, ‘description’))+ “\n”
36 Noticia = Noticia + ‘\nDate: ‘ + textOf(first(item,‘date’, DUBLIN_CORE))+ “\n”
37 Noticia = Noticia + ‘\nAuthor: ‘+ textOf(first(item,‘creator’, DUBLIN_CORE))+ “\n”
38 Noticia = Noticia +“---------------------------------------\n”
39 fn.write(Noticia)
40 fn.close()
Listado 3: lrss.py
A Brave New World” (“Un MundoFeliz”) es el nombre de la famosanovela de Aldous Huxley, en ella nosmuestra un mundo distinto y aterradorpero que parecía, y parece, cada vezmás cercano.
Nosotros no tenemos una visión tanpesimista del mundo, pero es probableque ese título (que se podría traducirliteralmente por «un nuevo y desafiantemundo») explique todo el revuelo queestá levantando AJAX. El término fueacuñado por Jesse James Garrett en elartículo [1] del cuadro de Recursos.
Durante mucho tiempo, las GUIs, lasInterfaces Gráficas de Usuario, handominado la informática. La gente quetrabajaba en la Web siempre estabaintentando convencer a todo el mundode que para la mayoría de los programas,una interfaz web bastaba. Pero los usua-rios estaban acostumbrados a ciertascaracterísticas, como el auto-completado
de campos o el arrastrar y soltar, queeran imposibles en la Web.
Conforme avanzaba el tiempo, nume-rosas empresas y personas proponíansoluciones. La lista es interminable:JavaScript, Java Applets, ActiveX, Tcl,VBScript, Macromedia Flash…
Pero todas fallaban de uno u otramanera. En el caso de Java, para ejecutarel Applet necesitabas tener instalado elJava Runtime Environment, y la mayoríade los usuarios no sabían ni qué eraaquello que se le pedía. Lo mismo ocu-rría con Flash.
Lo peor era que cuando estaba solu-cionado el tema de la instalación delsoftware adecuado, los desarrolladorescreaban, y crean, páginas horribles lle-nas de cosas moviéndose que distraen eirritan. Se sentían impulsados a usarhasta la última capacidad de las nuevasherramientas y acababan generandomonstruosidades.
Esta fase ya casi ha pasado y ahora sebusca la sencillez, y en el momento justosurgió AJAX. Para más información verurl [2] del cudro de Recursos.
¿Pero Qué es AJAX?Muy buena pregunta. Lo cierto es queAJAX ha estado delante de nuestras nari-ces todo el tiempo, esperando a quealguna mente despierta lo redescubriese.El acrónimo «AJAX» se compone de laspalabras «Asynchronous JavaScript andXML», término acuñado por Jesse JamesGarrett, y curiosamente su existencia sedebe a una de esas famosas violacionesde los estándares que suele realizarMicrosoft con sus productos.
Allá por 1998, Microsoft introdujo den-tro de sus productos una librería que lepermitía hacer consultas usando el pro-tocolo HTTP de manera autónoma yasíncrona. Cuando tu navegador accedea una página y ésta contiene código
INTEGRACIÓNAjax
33PYTHONW W W. L I N U X - M A G A Z I N E . E S
AJAX es la palabra de moda, Google usa AJAX, Yahoo usa
AJAX… todo el mundo quiere usar AJAX pero ¿lo usas tú? y
más importante aún ¿qué demonios es AJAX?
Por José María Ruíz y Pedro Orantes
La nueva tecnología web.
LimpiezaTotal
Gu
nn
ar P
ipp
el - 1
23R
F.co
m
INTEGRACIÓN Ajax
34 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Javascript, este código a su vez puedetraer información de esa u otras páginasde manera independiente. Si además sehace que este código permanezca en eje-cución respondiendo a eventos, tenemosentre manos la posibilidad de traer infor-mación al navegador sin recargar la pági-
na.Esto es útil para algunas tareas, pero
no demasiado, ya que a nuestro puzzlele faltan piezas. La primera pieza es laadopción de esta librería por casi todoslos navegadores, por lo tanto el códigopasa a ser de aplicación universal.
Además resulta que podemos modifi-car el contenido de la página en tiemporeal usando el denominado árbol DOM.Y por si fuese poco, cuando AJAX fuedefinido, los programadores comenzarona usar protocolos XML para comunicarsecon los servidores.
¿Qué quiere decir esto? Pues queahora, con AJAX, podemos cargar unapágina y, sin tener que recargarla, traer-nos información, modificar la página entiempo real e interactuar con servidoresremotos usando protocolos XML.
Básicamente, una vez cargada la pági-na web tenemos entre manos todas lasposibilidades de programación de una
GUI tradicional. Y todo esto sin necesi-dad de plugins ni instalaciones; toda estatecnología está en nuestros navegadoresesperando ser usada.
¿Cómo Encaja Python?Pues vamos a realizar un pequeño servi-dor de contenidos en Python que puedaser consultado usando AJAX. Crearemosuna web con algo de código Javascript,que a intervalos accederá a nuestro ser-vidor Python y modificará el aspecto dela página web.
Los 5 IngredientesLos cinco ingredientes necesarios paraelaborar nuestro producto son CSS,Javascript, HTML, XML y Python, comoaparecen en la figura 1, y cada uno tienesu función en esta obra.
HTML es la base sobre la que vamos atrabajar, y así definimos una página weben la que todo ocurrirá. De hecho, con elpaso del tiempo el propio HTML ha aca-bado convirtiéndose en una especie deplantilla donde campan a sus anchasCSS y Javascript.
01 #!/usr/local/bin/python02 03 import BaseHTTPServer04 import os05 import cgi06 07 class AJAXHTTPRequestHandler (BaseHTTPServer.
BaseHTTPRequestHandler):08 “”“09 Responde a peticiones HTTP10 “”“11 def do_GET(self):12 “Gestiona los GET”13 14 acciones = {15 “/” : [“envia_fichero”,”index.html”],16 “/ps.xml” : [“envia_comando”, “ps afx”],17 “/df.xml”: [“envia_comando”, “df”],18 “/who.xml”: [“envia_comando”,”who”],19 “/uname.xml”: [“envia_comando”,”uname -a”]}20 21 if self.path in acciones.keys():22 accion = acciones[self.path]23 (getattr(self,accion[0]))(self.path,accion[1])24 else:25 if (self.path[-3:] == “.js” or26 self.path[-4:] == “.css”):27 self.envia_fichero(“”,self.path[1:])28
29 else:30 self.envia_fichero(“”,”404.html”)31 32 def envia_fichero(self,ruta,fichero):33 # No usamos ruta, pero así simplificamos el código34 p = Pagina(fichero)35 self.enviar_respuesta(p.tipo(), p.contenido())36 37 def envia_comando(self,ruta,comando):38 c = Comando(comando)39 self.enviar_respuesta(c.tipo(), c.contenido())40 41 def enviar_respuesta(self, tipo, contenido):42 self.enviar_cabecera(tipo)43 self.wfile.write(contenido)44 45 def enviar_cabecera(self, tipo):46 self.send_response(200)47 self.send_header(“Content-type”,”text/” + tipo)48 self.end_headers()49 50 class Pagina:51 def __init__(self,nombre):52 self.nombre = nombre53 self.texto = “”54 fichero = file(self.nombre)55 self.texto = fichero.read()56 fichero.close()57
Listado 1: server.py
Figura 1: Esquema de nuestra aplicación AJAX con todos sus compenentes.
INTEGRACIÓNAjax
35PYTHONW W W. L I N U X - M A G A Z I N E . E S
CSS nos permite otorgar propiedadesvisuales a los elementos de HTML.
Javascript es el encargado de actuaren la máquina cliente, en el navegador,y puede modificar tanto el HTML comolas propiedades visuales que CSS defi-ne. Con la llamada XMLHttpResponse
sus atribuciones se han disparado.Ahora se ve como un lenguaje de pro-gramación de pleno derecho. En lospróximos años puede que adquieramucha más importancia de la que hatenido hasta ahora.
XML es el nuevo lenguaje estándar deintercambio de información.Prácticamente cualquier lenguaje dispo-ne ya de librerías para generar y analizardocumentos XML. Dentro del mundillo,AJAX se ha convertido en el estándarpara el intercambio de información conel servidor y para la serialización deobjetos con protocolos como JSON.
Y, como no, Python. En nuestro casose va a encargar tanto de realizar lastareas de servidor HTTP, como de reco-lectar información importante y confec-cionar con ella ficheros XML.
Tenemos que compenetrar todos estoselementos para realizar nuestro proyec-to. El objetivo es que los componentesHTML, Javascript, CSS y XML sean tanestáticos como sea posible, debido a quetodos ellos interactúan con el usuario.
Es en el servidor Python donde debe-mos aportar la flexibilidad necesariacomo para añadir nuevas características
sin tener que cambiar ninguno de losotros elementos.
La Parte de Python:BaseHTTPRequestPython dispone en sus librerías estándarde muchos «esqueletos» para distintostipos de servidores. Entre ellos encontra-mos BaseHttpRequest. Esta clase nospermite construir servidores HTTP sinexcesivo esfuerzo, así que la empleare-mos.
Pero no debemos usarla directamente,sino a través de la clase BaseHttp
RequestHandler, que es la encargada degestionar los eventos que se suceden enel servidor. Por tanto, heredaremos deella y crearemos una clase llamada
AJAXHttpRequestHandler, ver Listado 1(todos los listados de este artículo pue-den descargarse del articulo [4] del cua-dro de Recursos.
Un servidor HTTP recibe distintostipos de comandos, pero el que nos inte-resa es el comando GET. El cual se usapara solicitar información al servidor.Cada vez que se realice una petición GET
se invocará el método do_GET deBaseHTTPRequestHandler, así que lovamos a redefinir en nuestra clase.
Cuando se invoque do_GET, en lavariable de instancia self.path se encuen-tra la ruta solicitada por el cliente.Nosotros contrastaremos esta ruta contralas que aceptamos. Si no se encuentraentre ellas, devolveremos la célebre pági-
58 def contenido(self):59 return self.texto60 61 def tipo(self):62 tipo = “html”63 ext = self.nombre[-4:]64 if (ext == “html”):65 tipo = “html”66 elif (ext == “.xml”):67 tipo = “xml”68 elif (ext == “.css”):69 tipo = “css”70 return tipo71 72 class Comando:73 def __init__(self,comando):74 self.tuberia = os.popen(comando)75 self.xml = “”76 77 def contenido(self):78 # fichero XML
79 if not self.xml:80 self.xml = “<?xml version=\”1.0\” ?>”81 self.xml += “<salida>”82 linea = self.tuberia.readline()[:-1] # para quitar
el \n83 while linea:84 self.xml += “<linea>” + cgi.escape(linea) +
“</linea>”85 linea = self.tuberia.readline()[:-1]86 self.xml += “</salida>”87 return self.xml88 89 def tipo(self):90 return “xml”91 92 def test(HandlerClass = AJAXHTTPRequestHandler,93 ServerClass = BaseHTTPServer.HTTPServer):94 BaseHTTPServer.test(HandlerClass, ServerClass)95 96 if __name__ == ‘__main__’:97 test()
Listado 1: server.py (Cont.)
01 <html>02 <head>03 <title>Pruebas con AJAX</title>04 <link rel=”stylesheet”
href=”estilo.css”type=”text/css” />
05 <script language=”Javascript”06 src=”ajax.js”>07 </script>08 </head>09 <body>10 <div id=”documento”>11 <h3 id=”titulo”>Información
del sistema</h3>12 <input type=”button”
name=”button” value=”Procesos”13 onclick=”javascript:haz
Peticion(‘ps.xml’);” />
14 <input type=”button”name=”button” value=”Disco”
15 onclick=”javascript:hazPeticion(‘df.xml’);” />
16 <input type=”button”name=”button” value=”Usuarios”
17 onclick=”javascript:hazPeticion(‘who.xml’);” />
18 <input type=”button”name=”button” value=”Máquina”
19 onclick=”javascript:hazPeticion(‘uname.xml’);” />
20 <div id=”contenedor”>21 <div id=”datos”></div>22 </div>23 </div>24 </body>25 </html>
Listado 2: Fichero index.html
INTEGRACIÓN Ajax
36 PYTHON W W W. L I N U X - M A G A Z I N E . E S
na 404, indicando que la página solicita-da no existe. La información se devuelveusando el método self.wfile.write(), loque en él escribamos será devuelto alcliente que realizó la petición.
Además del fichero index.html, ofrece-remos una serie de servicios en forma deficheros XML. Estos servicios consistiránen la ejecución de un comando de siste-ma, y la conversión de su salida en unfichero XML. El formato del fichero serámuy sencillo:
<?xml version="1.0"?>
<salida>
<linea>linea de salida</linea>
...
<linea>linea de salida</linea>
...
<linea>linea de salida</linea>
</salida>
Por lo que generaremos el fichero XML
«a mano», sin hacer uso de librerías. Deesta manera, cuando el cliente solicite elfichero ps.xml, nuestro servidor ejecuta-rá el comando ps afx, creará el ficherops.xml con la salida del comando y se loenviará al cliente.
Definición de ServiciosPara permitir que la definición de serviciossea lo más simple posible, basta con intro-ducir una nueva entrada en el diccionarioacciones de la clase AJAX HTTP
RequestHandler con la siguiente estructura:
<ruta> : [<método_a_invocar>,U
<comando_a_ejecutar>],
Cuando se gestiona el comando HTTPGET, se busca en este diccionario la ruta.En caso de que esté presente, se ejecuta-rá el método almacenado usando comoparámetros la ruta y el comando. Estonos da gran flexibilidad, añadir un nuevoservicio consiste en introducir una nuevalinea de código.
Existe un detalle importante, todo ficherodevuelto usando HTTP debe tener unacabecera con una serie de líneas con forma-to llave: valor que dan información al clien-te, el navegador, sobre el fichero devuelto.Debido a problemas de espacio hemos deci-dido devolver sólo el formato del fichero.Para ello se invoca el método envia_cabece-
ra desde envia_respuesta antes de enviar elfichero en sí. Es de esta manera como elnavegador determina el tipo de fichero querecibe y sus características.
Figura 2: La clase BaseHTTPRequest genera
una entrada por comando.
01 // GLOBALES02 var http_request = false;03 04 function hazPeticion(url) {05 http_request = false;06 http_request= new XMLHttpRequest();07 if (http_request.overrideMimeType) {08 http_request.overrideMimeType(‘text/xml’);09 }10 11 if (!http_request) {12 alert(‘Error al crear la instancia de XMLHttp
Request.’);13 return false;14 }15 16 // Esto es un callback, que se dispara al terminar de17 // descargar el fichero xml.18 http_request.onreadystatechange = modifica
Contenido;19 http_request.open(‘GET’, url, true);20 http_request.send(null);21 }22 // Elimina todo elemento con id “linea”23 function vaciaContenido(){24 var d = document.getElementById(“contenedor”);25 while(document.getElementById(“linea0”) ||
document.getElementById(“linea1”)){26 nodo = document.getElementById(“linea0”);27 if (! nodo){28 nodo = document.getElementById(“linea1”);29 }30 var nodo_basura = d.removeChild(nodo);31 }32 }33 34 // Carga el resultado del XML
35 function modificaContenido() {36 if (http_request.readyState == 4) {37 if (http_request.status == 200) {38 vaciaContenido();39 40 var xmldoc = http_request.responseXML;41 var root =
xmldoc.getElementsByTagName(‘salida’).item(0);42 43 var fondo = 0;44 45 for(var i = 0; i < root.childNodes.length; i++){46 var nodo = root.childNodes.item(i);47 48 var contenedor = document.getElementById
(“contenedor”);49 var p = document.createElement(“p”);5051 // Truco para los colores ;)52 if (fondo == 0) {53 p.setAttribute(“id”,”linea0”);}54 else {55 p.setAttribute(“id”,”linea1”);56 }57 fondo = 1 - fondo;5859 var titulo = document.getElementById(“datos”);60 p.textContent = nodo.firstChild.data;61 62 contenedor.insertBefore(p,titulo);63 }64 65 } else {66 alert(‘Hubo un problema con la petición.’);67 }68 }69 }
Listado 3: Fichero ajax.js.
W W W. L I N U X - M A G A Z I N E . E S
Cuando arranquemos el servidor, veremos que van apare-ciendo mensajes correspondientes a los distintos comandosque se le mandan. La clase BaseHTTPRequest genera unaentrada por cada una. Ver figura 2.
Gestores de ServiciosPara gestionar los servicios se han creado las clases Pagina
y Comando, que responden a los mismos métodos. La pri-mera, Pagina, se encarga de las peticiones de ficheros detexto, que en nuestro programa se reducen al ficheroindex.html y sus ficheros supletorios, fichero Javascript yCSS. Básicamente, los carga en una variable y, mediante elmétodo contenido, las demás clases tienen acceso a los mis-mos. En este caso, el parámetro ruta no afecta a esta clase,pero se ha definido para que guarde compatibilidad con laclase Comando.
La clase Comando ejecuta el comando especificado y per-mite el acceso al texto devuelto por él mismo mediante elmismo método que la clase Pagina. De esta manera sonintercambiables.
Ambas clases poseen un método tipo() que devuelve eltipo 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 compruebala extensión de los mismos. En aras de la simplicidad, sóloconsideraremos tres extensiones.
Cuando un comando es ejecutado por Comando, se tieneespecial cuidado en utilizar la función cgi.escape sobre cadalínea. Esta función realiza algunas conversiones dentro deltexto que se le pasa para que pueda ser correctamentevisualizado por un navegador web. El problema radica enque ciertos caracteres, como pueden ser «<» or «”» sonespeciales y si no se «escapan», si no se preceden de un «\»,causarán problemas.
Y con esto, hemos definido un servidor web básico.Python nos permite realizar complejas tareas con poco códi-go y este es uno de esos casos. Vayamos ahora a por AJAXpara comprenderlo.
HTMLQuizá éste sea uno de los artículos donde Python tengamenor protagonismo, pero con cinco actores suele ser com-plicado. Veamos el HTML. La página index.html se puedever en el Listado 2.
Figura 3: Nuestra página devolverá los resultados de la consulta
sin recargar.
Nos referimos al ahora famosoXMLHttpRequest.
Si vemos el código del Listado 3, vere-mos un lenguaje que quizá nos recuerdaa Java. En realidad no tienen absoluta-mente nada que ver, aparte del nombre.En nuestro ejemplo observamos tres fun-ciones:• hazPeticion()• modificaContenido()• vaciaContenido()Javascript, además de muchas de lascaracterísticas presentes en otros lengua-jes (y algunas ausentes) dispone de unacolección de objetos que le permiten rea-lizar operaciones. A día de hoy los másimportantes son:• Manipulación DOM• Manipulación XML• XMLHTTPDOM permite a Javascript manipular, entiempo real, el contenido de la páginaweb. Puede añadir, modificar o quitaretiquetas y atributos, por lo que pode-mos operar sobre el documento de cual-quier forma posible. Javascript puedemanipular un fichero XML de igualforma que hace DOM.
Y la gran novedad, Javascript puederealizar conexiones ASÍNCRONAS con elservidor. Y resalto en mayúscula la pala-bra ASÍNCRONAS porque ahí está laclave.
Esto significa que podemos hace cone-xiones, traernos documentos XML delservidor y realizar operaciones DOM ode cualquier otro tipo, ¡cuando quera-mos!
El usuario carga su página web, yuna vez cargada, sin necesidad derecargarla, podemos modificarla anuestro antojo en base a informaciónque podemos pedir al servidor en cual-quier momento.
Volviendo a nuestras funciones, la fun-ción hazPeticion() recibe una url, crea elobjeto XMLHttpRequest y después dealgunas comprobaciones, asigna unafunción para que sea invocada cuando elfichero que esa url especifica sea com-pletamente descargado.
Esto significa que mientras leemosnuestra web, Javascript estará bajandoun fichero y, cuando finalice, llamará ala función modificaContenido.
¿Y qué hace esta función? Compruebael estado de la petición, (el estado 200 elde «todo correcto» y el 404 el de «lo sen-timos mucho, pero el fichero solicitado
no está disponible») y entonces obtieneel documento XML del objeto que gestio-naba la conexión.
Pero antes invoca a vaciaContenido(),que localiza toda etiqueta con las ids«linea0» o «linea1» y las elimina de lapágina. Hacemos esto porque a conti-nuación las volvemos a introducir en lapágina, pero esta vez con los datos fres-cos del servidor.
No queremos entrar en los detalles,ya que, en teoría, esto es un artículosobre Python, no Javascript, pero bási-camente esto es lo que hace el ficheroajax.js.
El fichero estilo.c, que aparece en ellistado 4, simplemente configura loscolores y características de algunas delas etiquetas para que el aspecto mejore.Y se acabó, aquí tenemos nuestra aplica-ción AJAX.
El resultado final será el que vemos enla figura 3. Cuando pulsemos cualquierade los botones, se cargará la salida detexto de la ejecución asociada a cada unode ellos en pantalla, pero sin recargar lapágina. Si queremos añadir un nuevocomando, sólo tenemos que introducir lalínea correspondiente en acciones en ser-ver.py y añadir un nuevo botón como losque ya existen en index.html.
Conclusión¿Es tan complicado eso de AJAX? ¡Porsupuesto que no! Lo que ocurre es quees una palabra que se está convirtiendoen un mito, pero no deja de ser una astu-ta combinación de programación en elservidor y cliente además del uso inten-sivo de XMLHttpRequest.
Poco a poco AJAX está poblando todaslas páginas webs y la mayoría de loscurrículos vitae. Quién sabe, lo mismodentro de dos años esa palabra tengatanto poder como otra palabra de cuatroletras: J2EE. ■
Básicamente, carga un ficheroJavascript, un fichero CSS y muestra untítulo junto a unos cuantos botones.Estos botones invocan acciones enJavascript.
Debemos fijarnos especialmente en eluso del atributo id en numerosas eti-quetas HTML. Gracias a estos idspodremos manipularlas medianteJavascript.
Javascript, desde Otra PerspectivaMucha gente ha tenido extraños encuen-tros con este lenguaje de programación.Es raro y, hasta hace no demasiado, nomuy útil. Te permitía modificar coloresen páginas web o poner insidiosos ban-ners. Por no hablar de las famosas venta-nas emergentes.Esto ha hecho que se haya ganado unafama muy mala, tal es así que casi todostenemos restricciones en nuestro navega-dor en torno a qué acciones puede o norealizar Javascript. Lo más normal esque tengamos uno de esos famosos blo-queadores de popups.Pero Javascript se ha reinsertado en lasociedad de los programadores por lapuerta grande gracias a un solo objeto.
INTEGRACIÓN Ajax
38 PYTHON W W W. L I N U X - M A G A Z I N E . E S
[1] Jesse James Garret define AJAX:
http:// adaptivepath. com/ ideas/
ajax-new-approach-web-applications
[2] Explicación de AJAX en Wikipedia:
http:// en. wikipedia. org/ wiki/ AJAX
[3] Sarissa: http:// sarissa. sourceforge. net/
doc/
[4] Listados de este artículo: http:// www.
linux-magazine. es/ Magazine/
Downloads/ Especiales/ 06_Python
Recursos
01 #documento{02 margin-left: 100px;03 }04 05 06 #titulo{07 text-decoration: underline;08 }09 10 11 12 #linea0 {13 margin: 0px;14 padding-left: 20px;15 background: #e0e0e0;16 font-family: monospace;17 }18 19 #linea1 {20 margin: 0px;21 padding-left: 20px;22 font-family: monospace;23 }24 25 #contenedor{26 border-style: dashed;27 border-width: 1px;28 width: 600px;29 border-color: black;30 }
Listado 4: Fichero estilo.css
En el año 2003,
Jim Hugunin leyó
que la plataforma
de Microsoft .NET
no era adecuada para
la creación de lenguajes
dinámicos (un lenguaje
dinámico es aquel al que
no debes decirle qué tipo
de variable vas a usar)
Hugunin se extrañó
bastante del
comentario porque
ya había creado un
intérprete de
Python, Jython,
para la máquina
virtual de Java. Este intérprete consiguió
cierto reconocimiento, y prueba de ello
es el artículo sobre Jython que aparece
en el número 7 de esta misma revista.
Python sobre .NETAsí que ni corto ni perezoso Jim se tomó el
comentario como algo personal y se puso
manos a la obra. Como resultado de su
esfuerzo, el 5 de Septiembre de 2006 apare-
ció la versión 1.0 de IronPython [1), nombre
que dio a su intérprete [2].
Antes de la aparición de la versión 1.0,
IronPython ya había llamado la atención de
gran cantidad de desarrolladores. La posibi-
lidad de crear aplicaciones gráficas o web
multiplataforma aprovechando los recursos
de .NET era demasiado interesante para
dejarla escapar.
Incluso Miguel de Icaza, de fama mundial
gracias a Gnome y Mono, reconoció la
importancia de IronPython para Mono en el
artículo que aparece referenciado en el
Recurso [3]: «Prácticamente cada nueva ver-
sión de IronPython ha expuesto las limita-
ciones de nuestro runtime (Mono), nuestras
librerías de clases o nuestros compiladores.
IronPython realmente ha contribuido para
que Mono se convierta en un mejor run-
time.»
Vamos a echar un buen vistazo a las posi-
bilidades de IronPython y veremos cómo
nos permite explotar el poder de las librerías
.NET, pero siempre desde nuestro lenguaje
favorito.
PreparativosPara poder hacer uso de IronPython necesi-
tamos instalarlo. IronPython es una imple-
mentación de Python sobre .NET, por lo que
necesitamos una implementación de .NET
para Linux.
Mono es la implementación libre de .NET
más famosa. Como vimos antes, Miguel de
Icaza está usando IronPython como banco
de pruebas para Mono. Esto nos asegura
que IronPython funcionará bastante bien
sobre Mono. Tenemos que instalar IronPy-
thon y Mono.
Falta un componente en la ecuación: libg-
diplus (ver Recurso [4]). En este artículo
haremos uso de la interfaz gráfica de usua-
rio que .NET pone a disposición de sus des-
arrolladores. Este sistema, llamado Win-
Forms, ha sido reimplementado en forma de
software libre en la librería libgdiplus. Sin
ella no podremos hacer uso de WinForms, y
por ello necesitamos instalarla.
Un detalle también algo molesto, ¿cómo
se sale del intérprete?
La verdad es que es
algo rebuscado. No
funcionan ni exit ni
quit. Cuando ejecute-
mos cualquiera de los
dos, seremos infor-
mados de que debe-
mos pulsar Control z
para salir del intér-
prete. Esto puede
resultar curioso para
todo aquel que haya trabajado mucho en la
consola de Linux, porque Control z sirve
para mandar a un programa a background.
La secuencia sería:
01 IronPython 1.0 (1.0) on
.NET 2.0.50727.42
02 Copyright (c) Microsoft
Corporation. All rights
reserved.
03 >>> quit
04 ‘Use Ctrl-Z plus Return to exit’
05 >>> exit
06 ‘Use Ctrl-Z plus Return to exit’
07 >>>
08 Suspended
09 >
10 >
Jim tuvo que crear este sistema para que
funcionara tanto en Windows como en
INTEGRACIÓN.Net
39PYTHONW W W. L I N U X - M A G A Z I N E . E S
.Net y Python con Ironpython
De Serpientesy Primates.NET está avanzando, y Python no se ha quedado atrás. 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é María Ruíz.
Figura 1: Hola Mundo con WinForms.
01 import clr02 03 from System.IO import *0405 fichero = File.OpenText(“mifichero.txt”)06 07 linea = fichero.ReadLine()08 09 while s:10 print linea
Listado 1: Mostrar el Contenido de un Fichero de Texto
Eric IsselÃ
ƒÂ©
e - 123RF
INTEGRACIÓN .Net
40 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Linux. Si ya tienes todos estos paquetes ins-
talados en tu distribución, podemos pasar al
siguiente paso… el infame «Hola Mundo».
Hola Mundo ConsolaLa primera sorpresa de IronPython es que
realmente es casi igual a Python… pero más
lento. Para ejecutar un programa .NET en
Linux debemos emplear el comando mono
que lo ejecuta dentro de la máquina virtual
de .NET.
> mono miprograma.exe
Probablemente, el comando ironpython de
vuestra distribución sea en realidad un script
shell que ejecuta el intérprete (llamado
ipy.exe) empleando mono. El resultado será:
IronPython 1.0 (1.0) onU
.NET 2.0.50727.42
Copyright (c) MicrosoftU
Corporation. All rightsU
reserved.
>>>
¿Microsoft Corporation? ¡Tranquilos, tran-
quilos! IronPython posee una licencia casi
libre, parte del Microsoft’s Shared Source.
Aunque su licencia no está aprobada por
OSI, Jim dice que la licencia que lo cubre
sigue todas las normas de OSI (ver Recurso
[5]).
Vayamos al grano, nuestro «hola mundo»
es reconocible:
>>> print “Hola Mundo”
Hola Mundo
>>>
Como el lector podrá apreciar, no ha cam-
biado nada. IronPython mantiene una gran
compatibilidad con Python. Pero no todo el
monte es orégano. Debido a que se ejecuta
sobre .NET, IronPython no puede acceder a
librerías de Python escritas en C. Esto es un
problema porque, por ejemplo, la librería
sys está escrita en C.
Otro detalle bastante molesto es que el
intérprete de IronPython no tiene historia, ni
autocompletación, ni … nada de nada. Esto
puede poner a más de uno de los nervios,
yo al menos me puse, por lo que es reco-
mendable trabajar en un fichero y ejecutarlo
con IronPython.
Librerías .NETHasta el momento no hemos hecho nada
especial, es hora de comenzar con lo intere-
sante. IronPython está escrito en C# sobre
.NET. Una de las virtudes de .NET es que
una vez que ha sido compilado a su len-
guaje intermedio, puedes hacer uso de cual-
quier librería escrita en cualquier lenguaje
para .NET.
IronPython tiene a su disposición todas
las librerías del proyecto Mono. Esto incluye
la librería para programación de aplicacio-
nes gráficas WinForms, las librerías para
programación web ASP.NET, estructuras de
datos, librerías de cifrado…, a las que hay
que sumar aquéllas que ha incorporado el
proyecto Mono, como por ejemplo las que
nos permiten empotrar el motor Gecko (de
Firefox) en una aplicación gráfica, de forma
que podemos crear un navegador web con
muy poco código.
Veamos un ejemplo de uso. Vamos a abrir
un fichero y a mostrarlo e imprimirlo en el
terminal, ver Listado 1. No es muy compli-
cado ¿verdad? Lo más importante de este
ejemplo es la sentencia:
import clr
que nos permite hacer uso de todas las libre-
rías de .NET.
Hola Mundo WinformsHasta ahora todo lo que hemos hecho era
fácilmente realizable con Python, a partir
de este momento observaremos cambios.
Vamos a realizar el mismo «Hola Mundo»
pero empleando WinForms. Microsoft ha
simplificado bastante el desarrollo de apli-
caciones gráficas con esta librería. IronPy-
thon lo ha simplificado aún más.
WinForms, ver Recurso [6], funciona de
forma similar a como lo hacen otras libre-
rías gráficas. Creas una ventana, dentro de
la cual creas un panel, dentro del cual se
pueden disponer widgets. Es muy parecido
a las muñecas rusas Matroskas. Veamos el
código en el Listado 2 y el resultado en la
Figura 1.
Comencemos por el principio. Una vez
importado clr, hemos de hacer algo que no
es normal en Python: debemos añadir refe-
rencias a las librerías de .NET que vamos a
utilizar. Para ello utilizamos el método
clr.AddReference() con el nombre de la libre-
ría que vamos a usar.
Winforms hace uso de «System.Drawing»
y de «System.Windows.Forms». Estas libre-
rías contienen todos los widgets necesarios
01 import clr
02 clr.AddReference(‘System.Drawing’)
03 clr.AddReference(‘System.Windows.Forms’)
04
05 from System.Drawing importColor, Point
06 from System.Windows.Forms import(Application, BorderStyle, But-ton, Form, FormBorderStyle,Label, Panel, Screen)
07
08 class HolaMundo(Form):
09 def __init__ (self):
10 self.Text = “Hola LinuxMagazine”
11 self.FormBorderStyle =FormBorderStyle.FixedDialog
12
13 pantalla = Screen.Get-WorkingArea(self)
14 self.Height = pantalla.Height /5
15 self.Width = pantalla.Width / 5
16
17 self.panel1 = Panel()
18 self.panel1.Location = Point(0,0)
19 self.panel1.Width = self.Width
20 self.panel1.Height =self.Height
21
22 self.generaSaludo()
23
24self.panel1.Controls.Add(self.label1)
25 self.Controls.Add(self.panel1)
26
27 def generaSaludo(self):
28 self.label1 = Label()
29 self.label1.Text = “Holalectores de Linux Magazine”
30 self.label1.Location =Point(20,20)
31 self.label1.Height = 25
32 self.label1.Width = self.Width
33
34 form = HolaMundo()
35 Application.Run(form)
Listado 2: «Hola Mundo» con WinForms
Figura 2: Visor de texto.
INTEGRACIÓN.Net
41PYTHONW W W. L I N U X - M A G A Z I N E . E S
para crear aplicaciones gráficas. Una vez
que añadamos las referencias a estas libre-
rías, podemos usarlas como cualquier libre-
ría de Python.
Importamos todos los widgets necesa-
rios: Application, BorderStyle, Button…
Para hacer referencias a posiciones en la
pantalla emplearemos la clase Point de
System.Drawing, expresando la posición en
pixels.
Con todas las librerías cargadas, pode-
mos comenzar inicializando nuestra clase
HolaMundo, que representa la ventana de
la aplicación. Comenzamos dándole
título, con self.Text, a la ventana. Defini-
mos el tipo de ventana que utilizaremos
con FormBorder Style indicando que será
fijo, nuestra ventana no se podrá redi-
mensionar.
Calculamos el tamaño de la ventana en
base al de la pantalla. Conseguimos los
datos de la pantalla mediante el método
Screen.GetWorkingArea(), y hacemos que
nuestra ventana tenga un quinto de la
altura (Height) y ancho (Width) de la
pantalla. Podríamos haber indicado el
tamaño mediante un número, digamos
100 pixels.
Creamos un panel que pasará a contener
todos los widgets que utilicemos. De nuevo
ajustamos su altura y anchura, así como su
posición dentro de la ventana. Como quere-
mos que ocupe toda la superficie de la ven-
tana lo posicionamos en (0,0), y le damos el
mismo ancho y la misma altura que la ven-
tana. He añadido un método, para simplifi-
car el código, que genera una etiqueta
donde realizamos el saludo. De nuevo el
proceso es repetitivo: texto de etiqueta, posi-
ción, altura y anchura.
Finalmente, añadimos la etiqueta al panel
y el panel a la ventana (esto último
mediante self.Controls.Add()). Con estas
últimas sentencias terminamos de definir
nuestra clase.
Para poder hacer uso de ella creamos una
instancia de HolaMundo y se la pasamos a
Application.Run(), que es un bucle sin fin
que se dedicará a gestionar los eventos
sobre la ventana.
La explicación es bastante más larga
que el texto, pero el lector se habrá dado
cuenta de lo simple que es realmente el
proceso. Incluso llega a ser aburrido por
repetitivo.
Pero hemos logrado nuestro objetivo, rea-
lizar una aplicación gráfica con un mínimo
01 import clr
02 clr.AddReference(‘System.Drawing’)
03 clr.AddReference(‘System.Windows.Forms’)
04
05 from System.IO import *
06 from System.Drawing import Color,Point
07 from System.Windows.Forms import(Application, BorderStyle,Button, Form, FormBorderStyle,Label, Panel, Screen, OpenFileDialog, DialogResult, TextBox,ScrollBars)
08
09 class LectorTXT(Form):
10 def __init__ (self):
11 self.Text = “Visor de texto LinuxMagazine”
12 self.FormBorderStyle =FormBorderStyle.FixedDialog
13
14 pantalla = Screen.GetWorkingArea(self)
15 self.Height = 300
16 self.Width = 400
17
18 self.panel1 = Panel()
19 self.panel1.Location = Point(0,0)
20 self.panel1.Width = self.Width
21 self.panel1.Height =self.Height
22 self.panel1.BorderStyle =BorderStyle.FixedSingle
23
24 self.generaLabel1()
25 self.generaLabel2()
26 self.generaBoton1()
27 self.generaAreaTexto()
28
29 self.panel1.Controls.Add
(self.label1)
30 self.panel1.Controls.Add(self.label2)
31 self.panel1.Controls.Add(self.boton1)
32 self.panel1.Controls.Add(self.areaTexto)
33
34 self.Controls.Add(self.panel1)
35
36 def generaAreaTexto(self):
37 texto = TextBox()
38 texto.Height = self.Height / 2
39 texto.Width = self.Width - 30 #para que no se salga
40 texto.Location = Point(20,110)
41 texto.Multiline = True
42 texto.ScrollBars =ScrollBars.Vertical
43 self.areaTexto = texto
44
45 def generaLabel1(self):
46 self.label1 = Label()
47 self.label1.Text = “Lector deficheros de texto Linux Magazine”
48 self.label1.Location =Point(20,20)
49 self.label1.Height = 25
50 self.label1.Width = self.Width
51
52 def generaLabel2(self):
53 self.label2 = Label()
54 self.label2.Text = “Ficheroseleccionado: ??”
55 self.label2.Location =Point(20,50)
56 self.label2.Height = 25
57 self.label2.Width = self.Width
58
59 def generaBoton1(self):
60 self.boton1 = Button ()
61 self.boton1.Name= ‘Botón 1’
62 self.boton1.Text = ‘Abrirfichero’
63 self.boton1.Location =Point(20,80)
64 self.boton1.Height = 25
65 self.boton1.Width = 100
66 self.boton1.Click +=self.abreFichero
67
68 def abreFichero(self, sender,event):
69 color = OpenFileDialog()
70 color.Filter = “Ficheros txt(*.txt)|*.txt”
71 color.Title = “Selecciona unfichero de texto”
72
73 nombre = “”
74
75 if (color.ShowDialog() ==DialogResult.OK ):
76 nombre = color.FileName
77 self.label2.Text = “Ficheroseleccionado: “ + nombre
78 # cargamos el texto
79 fichero = File.OpenText(nombre)
80 texto = “”
81
82 s = fichero.ReadLine()
83 while s :
84 texto += s
85 s = fichero.ReadLine()
86
87 self.areaTexto.Text = texto
88
89 form = LectorTXT()
90
91 Application.Run(form)
Listado 3: Visor de Texto Simple
INTEGRACIÓN .Net
42 PYTHON W W W. L I N U X - M A G A Z I N E . E S
de líneas de código. Vayamos a algo más
interesante.
Un Visor de TextosVamos a crear un visor de ficheros de texto
muy simple cuyo objetivo es explicar cómo
funciona la gestión de eventos en Win-
Forms. Este es quizá el punto débil de
muchas librerías gráficas, pero en el caso de
WinForms se ha simplificado mucho.
Las aplicaciones gráficas están vivas. No
son programas insensibles que realizan ope-
raciones, generan una salida y mueren. El
término exacto es «interactivas». Para poder
interactuar con el usuario deben ser capaces
de responder a acciones que el usuario rea-
liza.
A estas acciones se les denomina eventos.
Cuando creas una aplicación gráfica debes
responder ante estos eventos con acciones,
por evento existe una acción. Esta es la teo-
ría básica.
En lo que difieren las diferentes librerías
gráficas es en cómo se implementa la res-
puesta a los eventos. Los desarrolladores de
WinForms estaban hartos de tener que
escribir complicadas sentencias sólo para
hacer que cuando pulses un botón cambie
de color. Así que crearon el siguiente
diseño.
Cada widget de la aplicación posee una
serie de eventos ante los cuales responde.
Estos eventos vienen representados como
variables dentro del widget que guardan lis-
tas de acciones a realizar cuando tal evento
suceda. Así de simple. Veamos el código del
Listado 3. En el método generaBoton1 apa-
rece la sentencia de asignación de código a
un evento:
self.boton1.Click +=U
self.abreFichero
Hemos definido un botón,
y queremos que cuando
se pulse se ejecute el
método abreFichero. El
evento que se dispara
cuando pulsamos un
botón es Click, así que
usamos el operador +=,
que podríamos llamar
«suma y sigue», para aña-
dirlo a lista de funciones o
métodos a llamar cuando
aparezca ese evento. Es
realmente sencillo.
Pero, ¿qué hacemos
cuando pulsamos el botón
en el Listado 3? El objetivo
de este programa es crear un editor de textos
sencillo, ver Figura 2. Para ello, en la iniciali-
zación de la clase LectorTXT hemos puesto
en la ventana un par de etiquetas, una para
saludar, otra para decir qué fichero estamos
abriendo, un botón y un área de texto.
Cuando pulsamos en el botón aparecerá
el cuadro de diálogo de la Figura 3. Para cre-
arlo sólo hemos tenido que crear una instan-
cia de OpenFileDialog dentro de abreFi-
chero(). Este objeto define un cuadro de diá-
logo, pero no lo muestra, antes debemos
configurarlo. Sólo queremos mostrar fiche-
ros de texto, por lo que creamos un filtro que
únicamente permite ver ficheros de texto.
Para ello debemos almacenar en color.Filter
una cadena con un formato especial:
* “Texto a mostrar | filtro”
El filtro debe ser como los que empleamos
cuando usamos el terminal de Linux. El
símbolo * significa cualquier cadena de
texto, por lo que nuestro filtro solo permitirá
ver aquellos ficheros compuestos por cual-
quier cadena de texto y la terminación .txt,
que generalmente se emplea con los fiche-
ros te texto. El cuadro diálogo también tiene
un título, puesto que aparece en una ven-
tana independiente.
Cuando todo está configurado invocamos
color.ShowDialog(), que bloquea nuestra
aplicación hasta que el usuario ha escogido
un fichero. Esta llamada devuelve un resul-
tado, que debemos comprobar. En nuestro
caso, sólo seguimos si el resultado ha sido
DialogResult.OK. Si el usuario cierra el cua-
dro de diálogo, no se hará nada por ejem-
plo.
Si todo ha ido bien, entonces cogeremos
de color.FileName el nombre del fichero
seleccionado y emplearemos un código muy
parecido al que vimos en el Listado 1 para
cargar el texto en una variable. Cuando lo
tengamos, cargaremos el texto en la variable
self.areaTexto.Text, lo que provocará que se
modifique el contenido del área de texto.
ConclusiónPoder acceder a la enorme librería de .NET
con IronPython nos permite crear aplicacio-
nes gráficas multiplataforma. Una posibili-
dad realmente esperanzadora para todos
aquellos que quieran llevar sus desarrollos
en Python de un equipo a otro.
El lector puede profundizar en el
desarrollo de aplicaciones que empleen
WinForms desde IronPython en el Recurso
[7]. Es un tutorial, en inglés, en el que se da
un repaso a los conocimientos básicos de
desarrollo de aplicaciones gráficas mediante
WinForms.
Python está consiguiendo con IronPython
atraer a gran número de desarrolladores de
otros sistemas operativos, de forma que se
están comenzando a crear aplicaciones de
las que nosotros podremos disfrutar en
Linux gracias a Mono.
.NET se va estableciendo poco a poco en
el mundo empresarial como un estándar a
tener en cuenta. Pero esto no debe atemori-
zar a los que usen Python. Tanto si triunfa
.NET o Java, IronPython y Jython están ahí
para que Python siga vigente y demos-
trando al mundo que la programación no
tiene por qué ser complicada. ■
[1] Sitio de IronPython: http://www.
codeplex. com/ IronPython
[2] Jim Hugunin anuncia IronPython:
http:// blogs. msdn. com/ hugunin/
archive/ 2006/ 09/ 05/ 741605. aspx
[3] Miguel de Icaza anuncia la imple-
mentación libre de C#: http://tira-
nia.org/blog/archive/2007/Jan-11-
1.html
[4] API GDI+ para plataformas no Win-
dows: http://www.mono-pro-
ject.com/Libgdiplus
[5] La licencia Apache de IronPython:
http://www.codeplex.com/license?Pr
ojectName=IronPython
[6] WinForms: http://www.mono-pro-
ject.com/WinForms
[7] Tutoriales WinForms: http:// www.
voidspace. org. uk/ ironpython/
winforms/ index. shtml
[8] Listados de este artículo:
http:// www. linux-magazine. es/
Magazine/ Downloads/
Especiales/06_Python
Recursos
Figura 3: Diálogo de elección de fichero.
Algunos deciden dejar la habitación,
otros prefieren quedarse a tus espaldas,
nerviosos, con los brazos cruzados y la
mirada fija en ti. El problema está claro:
viene en camino un gran envío, proba-
blemente llegue hoy, con una gran canti-
dad de productos que se deben controlar
porque en unos días saldrán con destino
a un cliente. La última vez se hizo todo
el proceso con hojas de cálculo, pero fue
un desastre. Esta vez quieren estar pre-
parados.
Es necesario desarrollar una aplica-
ción y que funcione en menos de 2
horas.
Qt4 es la mejor opción.
Qt4Qt4 es la librería sobre la que se basa el
escritorio KDE4. Con licencia GPL, es
una alternativa perfecta para desarrollar
software libre. Posee algunas ventajas
envidiables:
• Es muy completa (incorpora código de
acceso a base de datos, tratamiento
svg, generación de PDF…)
• Es integrada (sólo necesitas QT, no
cientos de minipaquetes)
• PyQT es un proyecto maduro
• Posee un entorno de desarrollo rápido
de interfaces profesional: designer
Estas características hacen de Qt4 una
librería perfecta para el desarrollo rápido
de programas. Además es una librería
totalmente multiplataforma, podemos
desarrollar una sola vez y ejecutar donde
queramos.
DesignerUna de las ventajas de
Qt4 (ver Recurso [1])
es su designer. Es una
herramienta, curtida en
desarrollos profesiona-
les, que nos permite
comenzar diseñando el
aspecto gráfico de la
aplicación. Para ello
sólo tenemos que eje-
cutar el comando
designer-qt4, y apare-
cerá un programa com-
puesto por distintas
ventanas como las que
aparecen en la Figura
1.
Designer sirve para
crear ventanas, para lo
cual debemos crear en primer lugar una
ventana normal y corriente (llamada en
inglés «MainWindow»), como la que
aparece en la Figura 2, y con la que tra-
bajaremos durante todo el artículo. Por
defecto viene con un barra de estado y
otra de menú, con la que comenzare-
mos. Para ello hay que pulsar dos veces
INTEGRACIÓNQt
43PYTHONW W W. L I N U X - M A G A Z I N E . E S
Figura 1: Designer permite crear interfaces de aplicaciones rápida-
mente.
QT4 trae a Python nuevas mejoras en la creación de programas
¡DesarrolloRápido!
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 récord. La desesperación 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é María Ruíz
Ned
elc
u S
orin
, ww
w.s
xc.h
u
INTEGRACIÓN Qt
44 PYTHON W W W. L I N U X - M A G A Z I N E . E S
con el ratón sobre «Type Here» y escribir
«&Archivo». De esta forma habremos
creado nuestro primer menú. Dentro de
este menú introduciremos, de igual
forma, la acción «&Salir». El símbolo «&»
delante de las palabras indica que quere-
mos que Qt4 genere teclas rápidas para
ellas. Por ejemplo, al poner un «&»
delante de «Archivo» hacemos que su
primera «A» sirva como tecla rápida,
activando el menú cuando pulsemos
alt-a. El resultado sería el que vemos en
la Figura 3.
Vamos a trabajar con datos, así que lo
mejor que podemos hacer es emplear el
widget Table View, que nos permite tra-
bajar con datos tabulares y que podre-
mos conectar, más adelante, con nuestra
base de datos de forma casi directa. Para
ello tenemos que ir a la ventana principal
de Designer y buscar en Item Views el
widget Table View. Debemos arrastrarlo
hasta la ventana, quedando algo pare-
cido a lo que podemos ver en la Figura 4.
¿Queda mal, no? Lo ideal sería que ocu-
pase todo el espacio visible, para lo cual
tenemos que asignar un Layout (disposi-
ción) al espacio en el que pondremos
nuestro Table View. Hay que pulsar con
el botón derecho sobre el espacio gris
que rodea al Table View, ver Figura 5, y
pulsar en la última opción del menú: Lay
out. En ella escogeremos Lay out Verti-
cally, y nuestro Table View ocupará todo
el espacio.
Pulsamos dos veces con el ratón sobre
el Table View y aparecerá una ventana
que nos permitirá cambiar su nombre.
Esto es muy importante, ya que poste-
riormente nos referiremos a Table View
01 #!/usr/local/bin/python
02 # -*- coding: utf-8 -*-
03 import sys
04 from PyQt4 import QtCore
05 from PyQt4 import QtGui
06 from PyQt4 import QtSql
07 from gui import Ui_MainWindow
08
09 class Programa(QtGui.
QMainWindow):
10 def __init__(self,
parent=None):
11 QtGui.QWidget.__init__(self,
parent)
12
13 self.modelo = self.
generaModelo()
14
15 self.ui = Ui_MainWindow()
16 self.ui.setupUi(self)
17 self.ui.tabla.setModel
(self.modelo)
18 self.reajusta()
19
20 QtCore.QObject.connect(
self.ui.action_Salir,QtCore.
SIGNAL(“activated()”),QtGui.qAp
p, QtCore.SLOT(“quit()”) )
21 QtCore.QObject.connect(
self.ui.refrescar,QtCore.
SIGNAL(“clicked()”),self.refres
car )
22 QtCore.QObject.connect(
self.ui.nuevaLinea,QtCore.
SIGNAL(“clicked()”),self.nueva-
Linea )
23 QtCore.QObject.connect(
self.ui.eliminarLinea,QtCore.
SIGNAL(“clicked()”),
self.eliminarLinea )
24
25 def generaModelo(self):
26 self.conectaDB()
27 modelo = QtSql.
QSqlTableModel(None, self.db)
28 modelo.setTable(“inventario”)
29 modelo.setSort( self.
recordPrototipo.indexOf(“ean13”
), QtCore.Qt.AscendingOrder)
30 modelo.select()
31 return modelo
32
33 def conectaDB(self):
34 self.db = QtSql.QSql
Database.addDatabase(“QPSQL”)
35 self.db.setHostName(“rufus”)
36 self.db.setDatabaseName
(“inventario”)
37 self.db.setUserName
(“josemaria”)
38 self.db.setPassword(“”)
39 name = self.db.open()
40 query = QtSql.QSqlQuery(
“select * from
inventario”,self.db)
41 self.recordPrototipo =
query.record()
42
43 def reajusta(self):
44 self.ui.tabla.resize
ColumnsToContents()
45
46 def nuevaLinea(self):
47 fila = self.modelo.rowCount()
48 self.modelo.insertRow(fila)
49 self.reajusta()
50
51 def eliminarLinea(self):
52 index = self.ui.tabla.
currentIndex()
53 fila = index.row()
54 ean13 =
self.modelo.data(self.modelo.in
dex(fila, self.recordPro-
totipo.indexOf(“ean13”))).
toString()
55 nombre =
self.modelo.data(self.modelo.
index(fila, self.record
Prototipo.indexOf(“nombre”))).
toString()
56
57 if QtGui.QMessageBox.
question( self, ”Borrar linea”,
58 QtCore.QString(
“¿Desea borrar el producto #%1,
«%2»?” ).arg(ean13).arg
(nombre),
59
QtGui.QMessageBox.Yes|
QtGui.QMessageBox.No) ==
QtGui.QMessageBox.Yes:
60 self.modelo.removeRow(fila)
61 self.reajusta()
62
63 def refrescar(self):
64 self.modelo.select()
65
66 if __name__ == ”__main__”:
67 app = QtGui.QApplication(
≠sys.argv)
68 myapp = Programa()
69 myapp.show()
70 sys.exit(app.exec_())
Listado 1: Nuestro Programa
Figura 2: Nuestro “MainWindow” vacía y sin
widgets.
INTEGRACIÓNQt
45PYTHONW W W. L I N U X - M A G A Z I N E . E S
por este nombre, por lo que lo bautizare-
mos con uno que sea claro y conciso
(aunque no muy imaginativo): tabla.
Nuestra tabla, tal como aparece aquí,
nos permitiría ver datos, incluso editar-
los, pero no añadir ni tampoco borrar.
Para ello necesitamos botones que ini-
cien acciones externas. Emplearemos
Tool Buttons que nos permiten utilizar
imágenes en su interior. Estos botones
los solemos ver en las aplicaciones justa-
mente debajo del menú. Dispondremos 3
de estos botones: uno para refrescar los
datos, uno para añadir y otro para borrar.
El botón de refresco es muy importante,
puesto que al trabajar varias personas a
la vez en la base de datos puede darse el
caso de que necesitemos saber si se ha
introducido ya algún dato en particular.
Estos 3 botones los situaremos entre el
menú y la tabla, quedando como puede
apreciarse en la Figura 6. Pero, ¿por qué
aparecen así? ¿No sería mejor que apare-
ciesen a lo ancho? Sí, lo sería, pero hemos
indicado a designer que disponga los wid-
gets verticalmente. La solución consiste
en indicarle ahora que los disponga hori-
zontalmente, para lo cual sólo tenemos
que seleccionarlos con el ratón (como si
fueran ficheros, enmarcándolos en un
cuadrado de selección), pulsar el botón
derecho sobre ellos y en Lay outs selec-
cionar Break LayOut. Con esta acción
deshacemos la disposición que elegimos
anteriormente, pero para rehacerla, vol-
vemos a pulsar el botón derecho, y para
estos tres botones seleccionados elegimos
Lay out Horizontally, dejándolos como
aparecen en la Figura 7. Volvemos a pul-
sar con el botón derecho, aunque ahora
sobre el área gris entre los botones y el
Table View, y seleccionamos Lay Out Ver-
tically, quedando todos los widgets como
en la Figura 8, con lo que prácticamente
hemos acabado con el diseño de la inter-
faz. Nos quedan unos retoques.
A los botones hay que dar-
les nombre. Para hacerlo
sólo tenemos que pulsar el
botón derecho sobre ellos y
elegir Change objectName.
Los llamaremos, respectiva-
mente: «refres car», «nuevaLi-
nea», «eliminarLinea». El
último retoque consiste en
asignarles unos iconos, de
forma que sean identifica-
bles visualmente. Para esto
lo mejor es darse un paseo
por los directorios de iconos
de Gnome o Kde y seleccio-
nar los más acordes. Los
copiamos a nuestro directorio de
desarrollo, y en designer pulsamos la
combinación de teclas control-i. Apare-
cerá el Property Editor que nos permite
cambiar los parámetros de los widgets.
Estamos interesados en los de los boto-
nes, por lo que sólo tenemos que selec-
cionar un botón para que el Property Edi-
tor cambie para mostrar los parámetros
de ese botón. Vamos a cambiar el pará-
metro Icon, que está en la sección QAbs-
tractButton. Si pulsamos en él veremos
que a la derecha hay un botón que nos
permite seleccionar entre Choose
Resource y Choose File.... La primera
opción sirve para guardar en un solo
fichero, llamado de recursos, todos los
ficheros que vaya a necesitar nuestra
aplicación, lo que simplifica la instala-
ción. La segunda opción, más simple,
nos permite
indicar el nom-
bre del fichero
que empleare-
mos. Utilizare-
mos la segunda
opción, Choose
File..., por ser
más simple.
Seleccionamos
un fichero grá-
fico con el
icono, y hace-
mos lo mismo
para los otros
botones, ver
Figura 9.
Cuando guar-
demos nuestro
diseño generare-
mos un fichero
con extensión
.ui (User Inter-
face), que llamaremos gui.ui. Este
fichero describe la interfaz gráfica que
hemos diseñado, aunque no lo podemos
emplear directamente, sino que hay que
compilarlo. Para ello utilizamos el pro-
grama pyuic4:
josemaria@rufus> pyuic4U
gui.ui > gui.py
El resultado de compilar este fichero es la
generación de un fichero Python que rea-
liza todo lo necesario para que tengamos
nuestra interfaz gráfica funcionando.
Ahora necesitamos crear un programa
que, usando este fichero como librería,
gestione la aplicación que estamos cre-
ando.
Miramos nuestro reloj, sólo han
pasado 15 minutos, nuestros compañeros
Figura 3: Nuestro menú, esperando las
opciones.
Figura 4: Table View descolocada hacia el lado izquierdo.
Figura 5: El menú Lay out nos permite colocar correctamente los ele-
mentos.
INTEGRACIÓN Qt
46 PYTHON W W W. L I N U X - M A G A Z I N E . E S
miran por encima de nuestro hombro
tranquilos. Pero sabemos que esta inter-
faz necesita de un cerebro para contro-
larla, por el momento es sólo una
fachada.
La Base de DatosUsaremos una base de datos remota Pos t -
gresql para almacenar los datos, de esta
forma varias personas podrán trabajar a
la vez en el programa. En lugar de
emplear las librerías que existen para tra-
bajar con Postgresql en Python usaremos
Qt4. La tabla que crearemos será también
sencilla:
create table inventario (
ean13 char(13) primary key,
nombre varchar not null,
cantidad int not nullU
default 0,
constraintU
inventario_cantidad_positivoU
check(cantidad >= 0)
)
Qt4 trae consigo una librería de control
de base de datos muy avanzada: QtSql.
No se restringe a mandar código sql, ade-
más interactúa directamente con los wid-
gets de Qt usando un patrón de diseño
denominado MVC (modelo – vista – con-
trolador).
Esta técnica consiste en
separar el código de manipu-
lación de los datos, el código
que los muestra y el que
toma las decisiones. Qt pro-
vee dos clases diferentes, una
que representa los datos (el
modelo) y otra que repre-
senta la visualización de los
mismos (el visualizador),
dejando el control de la apli-
cación en nuestras manos. El
modelo es una entidad autó-
noma e independiente, no requiere de la
existencia de una base de datos. Pode-
mos crear un modelo con tablas y filas,
con relaciones y llaves, tal y como si
tuviésemos una base de datos. La razón
detrás de este diseño es la de unificar la
manipulación de los datos en toda la
aplicación, estandarizándola. Las aplica-
ciones empresariales siempre tienen que
interactuar con una base de datos, por lo
que esta decisión de diseño es cada vez
más común.
Por tanto, podemos crear nuestro
modelo de forma independiente, pero lo
mejor no es eso. Lo realmente intere-
sante es que podemos conectar ese
modelo con una base de datos y seguire-
mos manipulando los datos empleando
sólo el modelo. Esto quiere decir que no
tendremos apenas que escribir código
SQL, el modelo se encargará de ello
por nosotros. Nuestra aplicación
estará totalmente desligada de la
forma en la que se representan los
datos. Hoy pueden estar en Post -
gresql, mañana en MySQL y
pasado en un fichero XML, y no
tendremos que cambiar apenas el
código.
¿Cómo se hace todo esto? Vea-
mos el código del Listado 1. El
método conectaDB() muestra cómo
realizar la conexión a la base de
datos Postgresql. El código de Qt4
es sorprendentemente simple, com-
parado con otras librerías. Las últi-
mas dos líneas de código son algo
extrañas.
query =
QtSql.QSqlQueryU
(“select * from
inventario”,U
self.db)
self.recordPrototipo =U
query.record()
La primera de ellas realiza un select sobre
la tabla inventario, lo que no es muy
extraño, pero la segunda línea emplea el
resultado del select para generar un proto-
tipo. Hacemos esto porque necesitamos
saber el formato de una fila de la tabla
inventario, para saber el nombre de las
columnas y sus posiciones. Qt denomina
a esta información Record, y nos permitirá
realizar consultas posteriormente.
El método generaModelo() es más inte-
resante. Una vez realizada la conexión a
la base de datos necesitamos generar un
modelo de la tabla. Todos los modelos
descienden de una clase común, de forma
que son intercambiables. Existen dos
modelos de base de datos: QSqlTableMo-
del, más sencillo, y QSqlRelationalTable-
Model, que almacena también informa-
ción sobre las relaciones entre distintas
tablas. Nos conformamos con QSqlTable-
Model, puesto que nuestra tabla no está
relacionada con otras. Una vez tenemos el
modelo, seleccionamos la tabla que que-
remos manipular con setTable(). Es muy
interesante que los productos que aparez-
can en el Table View lo hagan ordenados
por EAN-13, lo que facilitará búsquedas y
comprobaciones a los usuarios. Para ello
ordenamos el modelo con el método set-
Sort(). Acepta dos parámetros, la posición
de la columna que se empleará para orde-
nar y el tipo de ordenación. Aquí entra en
juego el prototipo que generamos antes
de las filas de la tabla, que posee el
método indexOf() que nos devolverá la
posición de una columna dada. La orde-
nación puede ser QtCore.Qt.AscendingOr-
der (la primera fila tiene el valor más bajo
y la siguiente uno más alto) o
QtCore.Qt.DescendingOrder (lo contrario).
Con el modelo listo pasamos a cargarlo
con los datos, paso que realizamos invo-
cando select(), que realiza un select
sobre la tabla, y almacenando los datos
en el modelo.
Figura 6: Creamos tres nuevos botones…
Figura 7: … Y los alineamos a nuestro gusto.
Figura 8: Ésta será la disposición final de los botones y
tabla.
Volvemos a mirar nuestro reloj, han
pasado 40 minutos. Nuestros compañe-
ros se ponen nerviosos, no «ven» ningún
avance.
Conectando MundosTenemos que vincular nuestro código
con la librería gui.py que generamos
antes. Para ello la importamos:
from gui import Ui_MainWindow
y generamos un objeto de tipo Ui_Main-
Window al que nos conectamos
mediante el método setupUi(), ya que es
nuestro programa quien lleva la batuta.
Pasamos a tabla nuestro modelo de
datos, de forma que modelo y vista tam-
bién se contactan. A partir de ahora lo
que haya en la base de datos se corres-
ponderá con lo que se vea en el Table
View. El método reajusta() hace que el
Table View ajuste el ancho de sus colum-
nas para que se puedan ver todos los
datos. En caso contrario hará que las
columnas sean todas del mismo tamaño,
y algunos datos aparecerán incompletos.
Qt posee un sistema propio de progra-
mación denominado «Señales y Slots». Qt
se desarrolló en el lenguaje de programa-
ción C++, y pronto se dieron cuenta de
las deficiencias que poseía. En particular,
se dieron de bruces con la dificultad de la
programación de eventos en C++.
Como resultado de estos problemas,
TrollTech, la empresa creadora de Qt,
decidió crear un compilador y una serie
de instrucciones que hicieran la progra-
mación de interfaces gráficas más sim-
ple. Los detalles no son importantes, al
fin y al cabo este artículo es sobre
Python, pero el nuevo modelo de trabajo
se basa en eventos. Los objetos de Qt
poseen una serie de slots o conectores a
los que pueden llegar señales. Estos slots
realizan acciones cuando
una señal los dispara. Así,
cuando se hace click sobre
un botón, la señal ”click -
ed()” se pasa a un slot por
defecto del botón. Si quere-
mos que el botón realice una
acción diferente, podemos
definir una conexión de
”clicked()” con una función
o método que realice la
acción que deseamos. En
C++ este proceso requería
de la compilación del código
fuente con un compilador de
TrollTech, llamado moc, y poste-
riormente la compilación con un compi-
lador de C++.
En Python todo es más simple, sólo
tenemos que emplear el método
QtCore.QObject.connect() para especifi-
car el widget y la señal que emite con el
método que la gestionará.
En nuestro diseño tenemos 3 botones,
uno para refrescar, otro para añadir
líneas y otro para borrarlas. Por tanto,
tenemos que conectar la señal ”clicked()”
de estos botones a métodos que realicen
estas acciones.
La primera de ellas, refrescar, es la más
simple. Sólo tenemos que decirles al
modelo que vuelva a hacer un select()
para recargarse.
La segunda, nuevaLinea(), invoca al
método insertRow() del modelo para crear
una línea vacía en el Table View. Cuando
se rellenen todos los campos de esta linea
se guardará automáticamente en la base
de datos; el modelo se encarga de todo.
La tercera acción, eliminarLinea(),
localiza la posición actual en la tabla
obteniendo el índice de la celda que
tenga el foco, y a partir de él obtiene la
fila en la que se encuentra. Por seguridad
no borraremos directamente, sino que
presentaremos un cuadro de diálogo pre-
guntando al usuario si desea borrar la
fila, mostrando tanto el EAN-13 como el
nombre del producto. Para obtener
ambos volvemos a hacer uso del proto-
tipo para conseguir sus posiciones dentro
de la fila y poder recuperar sus valores.
Una vez confirmada la eliminación, sólo
tenemos que emplear el método remove-
Row() del modelo.
Poco más es necesario para que nues-
tra aplicación sea funcional, el resultado
final puede verse en funcionamiento en
la Figura 10.
Nuestros compañeros se levantan para
ver qué estamos haciendo y casi dan un
salto cuando observan que la aplicación
está modificando la base de datos en
tiempo real y de forma correcta. Nos
miran y, con cara de emoción, nos espe-
tan: «¡Menos mal! creíamos que todo iba
fatal, como no hiciste nada útil durante
la última media hora…». Así de sufrida
es la programación. Volvemos a mirar el
reloj, todo listo en menos de una hora.
Ahora, con más tranquilidad nos dis-
ponemos a hacer de la aplicación algo
más potente… y ¡a bautizarla!
ConclusiónEn muy pocas líneas de código, y gracias
al programa designer de Qt4, tenemos
una aplicación multiusuario funcional
que interactúa con una base de datos.
Qt4 ha dejado el listón muy alto para el
resto de frameworks de desarrollo. En
particular, es interesante su total integra-
ción con Windows, MacOsX y Linux,
permitiéndonos hacer uso de nuestro
software libre en cualquiera de las tres
plataformas de forma directa.
Qt4 es, sin duda, un opción realmente
interesante para prototipado de aplica-
ciones o para desarrollo rápido, bajo pre-
sión. Al fin y al cabo ha hecho que nues-
tro protagonista se convierta en un
héroe. ■
INTEGRACIÓNQt
47PYTHONW W W. L I N U X - M A G A Z I N E . E S
[1] ¿Qué es PyQt?:
http:// www. riverbankcomputing. co.
uk/ software/ pyqt/ intro
[2] Listado de clases con sus explica-
ciones y ejemplos: http:// www.
riverbankcomputing. co. uk/ static/ Docs/
PyQt4/ html/ classes. html
Recursos
Figura 9: Escogemos iconos representativos de las
acciones.
Figura 10: Nuestra aplicación en funcionamiento.
INFRAESTRUCTURAS Pyramid
48 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Django es el framework que más estácontribuyendo a la extensión del uso dePython. Al igual que ocurrió con Ruby yRuby on Rails, Django se ha convertidoen una gran baza para la comunidadPython y la excusa perfecta para probarPython. Está bien documentado, disponede gran número de extensiones y el res-paldo de grandes empresas ¿por quéquerría alguien crear un competidor?
La comunidad Python dista mucho de sermonolítica, aparecen múltiples alternativaspara casi todo. Lo curioso es que el novato enlos frameworks web ¡es Django! Antes de suaparición ya existían otros distintos, y Pyra-mid es el descendiente directo de algunos deellos [1].
Un “hola mundo” MinimalistaPodemos instalar Pyramid de muchas for-mas, pero la más cómoda desde el punto devista de Python consiste en crear un virtua-lenv e instalar en su interior Pyramid:
$ virtualenv --no-site-packages U
--distribute pruebas-pyramid
$ cd pruebas-pyramid
$ source bin/activate
(pruebas-pyramid)$ pip U
install pyramid
Con estos cuatro pasos dispondremos de unvirtualenv con las librerías que Pyramidnecesita para funcionar. Es posible crear unproyecto Pyramid con el comando paster (unproyecto como los que creamos con Django),pero como vamos a generar una primeraaplicación minimalista, sólo necesitamos enprincipio un fichero con el contenido queaparece en el Listado 1.
A diferencia de Django, Pyramid es un sis-tema bastante estructurado y centrado encomponentes. La configuración se efectúa através de una instancia de Configurator,
donde vamos añadiendo rutas, vistas y(como ya veremos) muchos otros tipos decomponentes. Configurator es la base sobrela que montamos nuestro sitio web.
El concepto de vista es sencillo, al igualque en Django, pudiendo usarse una funcióncualquiera que admita como parámetro unobjeto Request con la información de la peti-ción. Se distingue entre declarar una vista yemplearla en distintas rutas. Cada ruta tieneun nombre, route_name, que nos permiteconectarla con cualquier ruta. Así, los méto-dos add_route y add_view trabajan conjunta-mente para definir el comportamiento de laweb.
Una vez hemos acabado con laconfiguración, podemos arrancar el servidor.Para este sencillo ejemplo hacemos uso de lafunción serve() de paste, que implementa unservidor web en Python que acepta comoparámetro una aplicación WSGI, que obtene-mos llamando a make_wsgi_app() de Confi-
gurator.Las rutas pueden contener parámetros en
su interior que podemos capturar de distintasformas, como ya veremos.
Para poder arrancar el servidor sólo tene-mos que ejecutar el fichero como un pro-grama Python cualquiera e ir a la dirección127. 0. 0. 1:8080.
Creando un ProyectoLa generación de código fuente, el llamado«scaffolding», es, a día de hoy, un elementoindispensable de la mayoría de los frame-works web. Pyramid no provee directa-mente un sistema de scaffolding, lo que iríaen contra de su política de reutilización. Enlugar de ello hace uso de paster, un sistemaindependiente de scaffolding que compar-ten otros proyectos. Cuando instalamosPyramid con pip se instaló paster comodependencia, por lo que podemos usarlodirectamente:
$paster create --list-templates
Available templates:
basic_package: A basic U
setuptools-enabled package
paste_deploy: A web application U
deployed through paste.deploy
pyramid_alchemy: pyramid U
SQLAlchemy project using traversal
pyramid_jinja2_starter: U
pyramid jinja2 starter project
pyramid_routesalchemy:U
pyramid SQLAlchemy project using U
url
dispatch (no traversal)
pyramid_starter:pyramid starter U
project
pyramid_zodb: U
pyramid ZODB starter project
Con la opción --list-templates podemos verlos proyectos que paster puede generar.Pyramid nos ofrece varias alternativas,desde la más tradicional, empleando routes
y sqlalchemy, hasta otras más exóticas here-dadas de Zope, como pyramid_zodb o pyra-
mid_alchemy. La diferencia entre ambasposibilidades está en la forma en que seestructuran las urls y en la base de datos ausar.
Zope permite el uso de un sistema llamadotraversal que genera automáticamente lasurls empleando para ello las relaciones entrelos modelos de datos usados. La mayoría deframeworks web, en casi todos los lenguajesde programación, se decantan en su lugarpor la definición de las urls de forma explí-cita, para así tener más control sobre ellas.
Nosotros nos conformaremos con el enfo-que tradicional, por lo que podemos crear elproyecto con:
$ paster create -t U
pyramid_routesalchemy U
ejemplo
Como resultado obtendremos un directoriollamado ejemplo que albergará nuestro pro-yecto, y en su interior un módulo Pythonllamado también ejemplo. Para poder arran-car el proyecto tenemos que generar pri-mero un fichero de configuración e instalarlos paquetes necesarios mediante elcomando:
$ cd ejemplo
$ python setup.py develop
Pyramid trae dos configuraciones: «develop»para desarrollo y «production» para el entorno
Uno de los rivales de peso de Django está creciendo en popularidad poco a poco.
Por José María Ruíz
El framework que no fue construido por alienígenas
Pyramid
morg
uefile
.com
de producción. Cada una aparecerá como unfichero con extensión .ini que nos permitiránconfigurar el proyecto. Mientras que otros fra-meworks, como Django, prefieren que laconfiguración se haga usando código Python,en Pyramid decidieron optar por seguirusando ficheros de configuración .ini.
Una vez haya finalizado el proceso pode-mos arrancar el servidor web con paster:
$ paster serve U
development.ini
Podemos ver la página generada en la rutahttp://localhost:6543. Esta página incluye a laderecha una pestaña que nos da acceso aldevelopment toolbar de Pyramid, el cual nosproporcionará información muy valiosadurante el desarrollo de la aplicación, asícomo enlaces a la documentación de Pyra-mid.
Renderers,Vistas yPlantillasPyramid permiteconfigurar dife-rentes renderers
que pueden convi-vir en el mismoproyecto. Por ejem-plo, podemos con-figurar varios siste-mas de plantillas ala vez (Mako, Cha-maleon, Jinja2,…)y hacer que Pyra-mid seleccione el
correcto basándose en la extensión de laplantilla a usar.
En este ejemplo usaremos Jinja2, un sis-tema de plantillas muy parecido al empleadopor Django pero más flexible y potente. Pri-mero tenemos que instalar la extensión dePyramid para Jinja2:
$ pp install pyramid_jinja2
Una vez instalada debemos indicar a Pyra-mid que cargue la extensión y con qué fiche-ros queremos que use Jinja2 (en nuestrocaso los que acaben en «.html»). Debemosmodificar el fichero ejemplo/ ejemplo/
__init__. py, que alberga la configuraciónpara nuestro proyecto, y poner dentro demain:
config = U
Configurator(settings=settings)
config.include(‘pyramid_jinja2’)
config.add_renderer(‘.html’,U
‘pyramid_jinja2.rendererU
_factory’)
config.add_static_view(‘static’U
, ‘ejemplo:static’)
config.scan()
config.add_route(‘portada’, ‘/’)
La llamada a add_renderer() es la que nospermite indicar a Pyramid que las plantillascon extensión .html deberán ser renderizadasempleando Jinja2. Además llamamos ascan(), que se encargará de buscar las vistasque definamos y nos ahorrará el tener queañadirlas una a una con add_view(), comovimos en el Listado 1. Pero para que esto seaposible, nuestra vista debe cambiar la formade trabajar; debemos poner el siguientecódigo en ejemplo/ejemplo/views.py:
from pyramid.view import U
view_config
@view_config (route_name = U
”portada”, renderer = U
’ejemplo:templates/portada.html’)
def portada(request):
return {‘saludo’:‘Hola mundo!!’}
Para indicar el route_name y la plantilla queusaremos en la vista portada usaremos eldecorador @view_config(). En él podemosdefinir todos los parámetros que necesitaPyramid para usar la vista. Con el parámetrorenderer indicamos que queremos la plantillaportada.html, que debe estar dentro del direc-torio templates del módulo ejemplo. Cada
INFRAESTRUCTURASPyramid
49PYTHONW W W. L I N U X - M A G A Z I N E . E S W W W . L I N U X - M A G A Z I N E . E S
Figura 1: Página por defecto de Pyramid y debug_toolbar.
01 from paste.httpserver import serve
02 from pyramid.configuration import Configurator
03 from pyramid.response import Response
04
05 def hola_mundo(request):
06 nombre = request.matchdict.get(‘nombre’, ‘mundo’)
07 return Response(‘Hola {0}!’.format(nombre))
08
09 if __name__ == ‘__main__’:
10 config = Configurator()
11 config.add_route(‘index’, ‘/’)
12 config.add_route(‘hola’, ‘/{nombre}’)
13 config.add_view(hola_mundo, route_name=’hola’)
14 config.add_view(hola_mundo, route_name=’index’)
15
16 app = config.make_wsgi_app()
17 serve(app, host=’0.0.0.0’)
Listado 1: Ejemplo de Pyramid Básico
01 from pyramid.view import view_config
02
03 @view_config(route_name=”portada”,
04 renderer=’ejemplo:templates/portada.html’)
05 def portada(request):
06 saludo = ‘Hola mundo!!’
07 if request.POST:
08 nombre = request.params. get (‘nombre’, saludo)
09 if nombre:
10 saludo = “Hola {0}”.format(nombre)
11
12 return {‘saludo’: saludo}
13
14 @view_config(route_name=”formulario”,
15 renderer=’ejemplo:templates/formulario.html’)
16 def formulario(request):
17 return {}
Listado 2: Vista que Procesa Parámetros
INFRAESTRUCTURAS Pyramid
50 PYTHON W W W. L I N U X - M A G A Z I N E . E S
La extensión pyramid_
jinja2 se encarga de con-vertir la ruta ejemplo:tem-
plates /base. html en unaruta del sistema de fiche-ros que Jinja2 pueda uti-lizar. Vamos a añadir unanueva vista para demos-trar cómo funcionan losenlaces y los formularios(ver Listado 2, Listado 3y Listado 4).
Creamos una nuevavista que apuntamos ala ruta por defecto de nuestro proyecto. Deesta forma la página principal mostrará unformulario para que podamos pasar un nom-bre. En el Listado 4 podemos ver el código dela plantilla ejemplo/ejemplo/templates/for-
mulario.html, donde generamos la url queprocesará el formulario así:
<form action = U
”{{request.route_url U
(‘portada’)}}” method=”post”>
A request.route_url() le pasamos elroute_name de la vista que queremos que
procese el formulario. De esta forma pode-mos decidir cambiar qué vista lo procesarásiguiendo cualquier criterio que queramos,puesto que la asignación de un route_name auna vista puede variar durante la ejecuciónde la llamada (por ejemplo, empleando crite-rios de seguridad, o si el usuario está regis-trado o no).
En el Listado 2 podemos observar que eltratamiento de los datos es rudimentario.Pyramid no cuenta con una librería procesa-dor de formularios como Django, sino quedependemos del uso de una librería externa.Existen varias opciones posibles, pero las
módulo puede disponer de sus propias plan-tillas independientes, lo que aumenta lamodularidad del diseño. Además, como laplantilla acaba en .html, Pyramid empleará elrenderer Jinja2.
Jinja2 permite establecer herencia entreplantillas, por lo que crearemos una plantillaejemplo/ejemplo/templates/base.html:
<html>
<body>
<h1>Bienvenido<h1>
<hr/>
{% block contenido %}
{% endblock %}
</body>
</html>
Y otra plantilla más llamada ejemplo/ejem-
plo/templates/portada.html:
{% extends “ejemplo:U
templates/ base.html” %}
{% block U
contenido %}
<h2>{{saludo}}U
</h2>
{% endblock %}
Figura 2: El comando “top” funcionando en nuestro navega dor.
01 config = Configurator(settings=settings)
02 config.include(‘pyramid_jinja2’)
03 config.add_renderer(‘.html’, ‘pyramid_jinja2.ren-derer_factory’)
04 config.add_static_view(‘static’, ‘ejemplo:static’)
05 config.scan()
06 config.add_route(‘portada’, ‘/hola’)
07 config.add_route(‘formulario’, ‘/’)
Listado 3: Configuración Necesaria para el Listado 2
01 {% extends “ejemplo:templates/base.html” %}
02 {% block contenido %}
03 <form action=”{{request.route_url(‘portada’)}}”method=”post”>
04 <p>
05 <input type=”text” name=”nombre”/>
06 <button type=”submit”>enviar</button>
07 </p>
08 </form>
09 {% endblock %}
Listado 4: Plantilla formulario.html
01 {% extends“ejemplo:templates/base.html” %}
02 {% block extrahead %}
03 <scriptsrc=”http://code.jquery.com/jquery-1.6.2.min.js”></script>
04 <scriptsrc=”http://cdn.socket.io/sta-ble/socket.io.js”></script>
05 <script>
06 var socket = null;
07 var txt = null
08 $(function() {
09 socket = new io.Socket(null,{});
10 socket.on(‘connect’, func-
tion() {
11 socket.send({type: “connect”,userid: 123});
12 });
13 socket.on(‘message’, func-tion(obj) {
14 if (obj.type == “showdata”) {
15 console.log(“Message”,JSON.stringify(obj));
16 txt = obj.txt;
17 $(‘#htop’).html(txt);
18 }
19 });
20 socket.connect();
21 });
22 </script>
23 <style>
24 #htop {
25 font-family: monospace;
26 font-size: 12pt;
27 background: black;
28 color: green
29 }
30 </style>
31 {% endblock %}
32 {% block contenido %}
33 <h2>htop</h2>
34 <pre id=”htop” ></pre>
35 {% endblock %}
Listado 5: Plantilla top.html
INFRAESTRUCTURASPyramid
51PYTHONW W W. L I N U X - M A G A Z I N E . E S
más conocidas son FormEncode y FormAl-
chemy [5] [6] . En nuestro caso he decididoprocesar «a mano» la petición.
Un Ejemplo Más Potente¿De verdad compensa la flexiblidad que nosaporta Pyramid? Con Django es todo muchomás sencillo, puesto que las decisiones sobrequé librerías emplear ya han sido tomadas.Además, todas las librerías están controladaspor el proyecto, por lo que su integración esperfecta.
Personalmente creo que Pyramidcomienza a rendir cuando necesitamos hacercosas que no son tradicionales. Uno de losproblemas de Django consiste en que fuediseñado en un entorno muy bien definido:un periódico. Django no se encuentra muybien preparado para el entorno actual, dondetecnologías como HTML5 o websocketscomienzan a ser cada vez más importantes.Como ejemplo final de Pyramid vamos acrear una página que empleará socket.io [7]para mandar datos a nuestro navegador entiempo real, lo que definitivamente no es latípica página web tradicional.
Instalamos las librerías necesarias:
pip install gevent U
gevent-websocket gevent-socketio
Éstas nos permitirán arrancar nuestro pro-yecto con un servidor basado en gevent enlugar de usar paster, lo que nos permitirá res-ponder a consultas continuadas sin necesi-dad de cerrar el canal de comunicación conel navegador.
En el Listado 5 podemos ver el código dela plantilla htop.html. En ella cargamos tantojquery como la librería socket.io.js, que seencarga de establecer un canal continuo decomunicación entre el navegador y el servi-dor. Si el navegador soporta websockets, losusará, pero en caso contrario tratará de inter-actuar usando otros mecanismos. Lo mejorde socket.io es que nos permite programartoda la interacción mediante mensajes.Cuando recibimos un mensaje showdata,cambiamos el texto de la etiqueta con id htop
por el que recibimos del servidor. Realmentesencillo ¿verdad? No tenemos que ser cons-cientes ni siquiera sobre cómo se recibe elmensaje o cómo se procesa.
El Listado 6 muestra el código donde real-mente ocurre la magia. Creamos una sub-clase de SocketIOContext, donde conecta-mos y mandamos un mensaje connected alnavegador remoto. Seguidamente definimosla función sendhtop, que será ejecutada porgevent como si fuese una hebra indepen-diente para cada conexión que recibamos.En dicha función ejecutamos htop con dosparámetros que le indican que sólo nosmuestre el estado de los procesos una vez ypare su ejecución. De esta manera podemosobtener una instantánea de la situación denuestro ordenador. La salida de htop la man-damos en un mensaje showdata al navega-dor e indicamos a gevent que espere 1segundo.
Todo esto se ejecutará en un bucle infinitomientras el navegador esté conectado, cosaque sabremos con el resultado de self.io.con-
nected(). De esta forma nuestro navegador
mostrará htop como en la urlhttp://127.0.0.1:6543/htop ¡como si se estu-viese ejecutando en él! Es fácil imaginar lasposibilidades de esta tecnología (ver Listado7 para configuración).
ConclusiónSi bien Django es el framework web domi-nante en Python, Pyramid puede ser unaalternativa muy interesante si necesitamosrealizar una aplicación web que no sea tradi-cional. Su sistema basado en componentesnos da acceso a librerías de alta calidad yrealmente potentes que normalmente sonun fastidio integrar. En este artículo sólohemos rascado la superficie de Pyramid –que cuenta con librerías realmente avanza-das para autenticación, por ejemplo – peroespero que el lector haya podido dar sus pri-meros pasos con el framework… ¡que no hasido construido por alienígenas! [8]. ■
[1] Pyramid: https:// docs. pylonsproject. org/ projects/ pyramid/ dev/
[2] Pylons: https:// www. pylonsproject. org/
[3] Turbogear: http:// turbogears. org/
[4] Repoze.bfg: http:// bfg. repoze. org/
[5] FormEncode: http:// www. formencode. org/ en/ latest/ index. html
[6] FormAlchemy: http:// code. google. com/ p/ formalchemy/
[7] Socket.io: http:// socket. io/
[8] Pyramid, not built by aliens!: https:// pylonsproject. org/ denials/ pyramid. html
Recursos
01 config = Configurator(settings=settings)
02 config.include(‘pyramid_jinja2’)
03 config.add_renderer(‘.html’, ‘pyramid_jinja2.ren-derer_factory’)
04 config.add_static_view(‘static’, ‘ejemplo:static’)
05 config.scan()
06 config.add_route(‘portada’, ‘/hola’)
07 config.add_route(‘formulario’, ‘/’)
08 config.add_route(‘socket.io’, ‘socket.io/*remain-ing’)
09 config.add_route(‘top’, ‘/htop’)
Listado 7: Configuración Necesaria para el Ejemplo con socket.io
01 from pyramid_socketio.io importSocketIOContext, socketio_manage
02 import gevent
03
04 class ConnectIOContext(SocketIO-Context):
05 def msg_connect(self, msg):
06 self.msg(“connected”)
07 import subprocess
08
09 def sendtop():
10 prev = None
11 while self.io.connected():
12 cmd = ‘top -b -n 1’
13 p = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE)
14 txt = p.communicate()[0]
15 self.msg(“showdata”, txt = txt)
16 gevent.sleep(1.0)
17
18 self.spawn(sendtop)
19
20 @view_config (route_name=”socket.io”)
21 def socket_io(request):
22 print “Socket.IO request run-ning”
23 retval = socketio_manage(Connec-tIOContext(request))
24 return Response(retval)
25
26 @view_config(route_name=”top”,
27 renderer=’ejemplo:templates/htop.html’)
28 def htop(request):
29 return {}
Listado 6: Conexión socket.io en views.py
INFRAESTRUCTURAS Django
52 PYTHON W W W. L I N U X - M A G A Z I N E . E S
En el verano de 2005 surgió en el mundo
del código abierto un nuevo web framework
[1]. Sólo tres años después desde su publi-
cación, Django tiene ya el suficiente atrac-
tivo como para alentar la formación de la
Django Software Foundation [2]. Con la for-
mación de la DSF, Django pasa a formar
parte de una impresionante lista de proyec-
tos con fundación propia, entre los que se
encuentran Apache, Perl y Python.
¿Qué es Django?Django es un “framework” para el desarrollo
web con Python. Se trata de un juego de
librerías que permiten al desarrollador tra-
bajar en las partes de una aplicación que
verdaderamente importan sin tener que pre-
ocuparse por la infraestructura subyacente.
Django usa el patrón MVC como otros
muchos frameworks (Ruby on Rails y los
distintos frameworks en Perl y PHP).
Una de las funcionalidades punteras de
Django es su increíble interfaz de adminis-
tración, que se construye automáticamente
para nosotros. En este artículo recorremos
los pasos necesarios para la creación de una
pequeña aplicación de tipo Twitter, con la
que veremos en acción esta interfaz de
administración.
Son muchos los sitios web de alto nivel
que emplean Django en su desarrollo [3],
como EveryBlock.com, Pownce.com o Tab-
blo.com. Además, es el framework predeter-
minado incluido en el AppEngine de Google
(tenemos entendido que Google lo usa tam-
bién internamente en algunas tareas).
Django es, además, la fundación del gestor
de contenidos comercial Ellington, usado en
varias organizaciones de gran tamaño del
mundo de las noticias, como por ejemplo el
Washington Post.
Jacob Kaplan-Moss, presidente de la
Django Software Foundation y uno de los
creadores de Django, dijo que la fundación
fue creada con el fin de que el proyecto
pudiese dar el siguiente paso en su ciclo de
vida como proyecto de código abierto.
“Obviamente hemos tenido éxito a la hora
de atraer a una comunidad grande, vibrante,
por lo que sentimos que era hora de que
Django perteneciese a la comunidad. Con
la fundación se garantiza su perpetuidad,
incluso aunque algunas personas o algunas
compañías perdiesen interés”, comentó.
Kaplan-Moss señala que el proyecto
acepta ahora donaciones para mejorar
Django y, en un futuro próximo, la fundación
soportará Django mediante reuniones de
desarrolladores, mítines y otras actividades
comunitarias. Muchas de estas reuniones de
desarrolladores han tenido lugar antes de la
publicación de Django 1.0 -- y Kaplan-Moss
comenta que la fundación ayudará a que las
personas más indispensables puedan colabo-
rar al tiempo que asisten a las reuniones. “Si
la fundación ayuda a Django a avanzar, aun-
que sólo sea un poco más rápido, con eso me
bastará”, dijo Kaplan-Moss.
ComencemosLa publicación de la versión 1.0 oficial de
Django se produjo el día 2 de Septiembre de
2008, tal y como estaba planeado en su hoja
de ruta.
Siempre podemos obtener, usando Sub-
version, el código más reciente:
svn checkout U
http://code.djangoproject.com/U
svn/django/trunk/
Independientemente de la versión que use-
mos, la instalación de Django es muy senci-
lla. Estando conectados a Internet, ejecuta-
mos como root:
python setup.py install
para instalar Django en el directorio site -
packages o donde sea que tengamos la ins-
talación de Python. En nuestro ejemplo usa-
remos SQLite como base de datos. De todas
formas, Django soporta perfectamente Post -
gresSQL y MySQL.
Para usar SQLite, instalamos el paquete
pysqlite2 [5] y seguimos las instrucciones de
la instalación.
Django distingue entre proyectos y aplica-
ciones. Por ejemplo, si hiciésemos un sitio
web de gran tamaño, con una sección for-
mada por un blog, un foro o comercio
online, entonces el sitio en sí sería el pro-
yecto, mientras que el blog, el foro y el e-
Los creadores del proyecto Django nos hablan de la formación de la Django Software Foundation. Y
mostramos cómo comenzar con esta infraestructura web. Por Frank Wiles
Django Software Foundation y Django
Guitarrazos
Ale
xey K
lem
en
tiev, F
oto
lia
comercio serían las aplicaciones. En realidad
sólo es una forma de organizar los subpro-
yectos dentro del proyecto general.
Para iniciar un nuevo proyecto ejecuta-
mos
django-admin.py startproject U
miprueba
con lo que se crea el directorio miejemplo
con unas pocas herramientas y archivos de
configuración predeterminados. Luego
necesitamos que Django genere los de la
aplicación, que llamaremos Prueba. Para
ello, ejecutamos desde el directorio
miprueba:
python manage.py startapp Prueba
En miprueba/ settings.py, colocamos DATA-
BASE_ENGINE = ‘sqlite3’ y a
DATABASE_NAME le ponemos la ruta com-
pleta a miprueba/ prueba.db, el archivo de
SQLite en el que vamos a guardar nuestra
base de datos. La ruta completa depende del
directorio en el que hemos ejecutado el
startproject inicial. Añadimos dos elementos
a la lista INSTALLED_APPS: django.con-
trib.admin para la interfaz de administra-
ción y la aplicación miprpueba.Prueba, ase-
gurándonos de que añadimos las comas.
Después de definir qué base de datos
vamos a usar, tenemos que construir nues-
tro modelo (Model), que es un objeto de
Python que define las tablas y columnas de
SQL y su relación. Puesto que la aplicación
sólo va a tener una tabla, sólo definimos
una clase. El archivo miprueba/ Prueba/
models.py debería parecerse al que se mues-
tra en el Listado 1.
Primero importamos los asistentes de
modelo de Django y definimos la clase
Prueba, que contiene una columna de fecha
y otra de texto para la entrada real. Luego
definimos el método especial __unicode__,
que le dice a Model cómo mostrar una ins-
tancia del objeto en formato de cadenas (en
este caso, sólo imprime la fecha y la entrada
completa). Esta será la información que
usará el admin en el momento de mostrar
los listados con las entradas de la base de
datos. La clase vacía Admin indica a Django
que queremos hacer uso de la interfaz de
administración.
Para comprobar lo que llevamos hecho,
validamos los modelos mediante:
python manage.py validate
Si todo está bien, debería devolvernos 0
errors found. Django ya puede montar las
tablas de la base de datos. Para ello, introdu-
cimos
python manage.py syncdb
Vemos varias líneas con Creating table,
algunas de las cuales pertenecen a permi-
sos de usuario/ grupo, otras a admin y la
última para la tabla Prueba. Es ahora
cuando Django nos insta a crear el super -
usuario para la interfaz de administración.
Debemos recordar el nombre de usuario y
la contraseña, ya que las vamos a necesitar
luego.
Después de crear las tablas para el
modelo y la base de datos, habilitamos la
interfaz de administración. Lo hacemos des-
comentando las tres líneas del archivo
miprueba/ urls.py que se creó al ejecutar
startproject. Las tres líneas están etiqueta-
das, indicándonos que hay que descomen-
tarlas para que se habilite la administración.
El archivo urls.py es con el que Django,
mediante expresiones regulares, enlaza las
diferentes URLs con las distintas partes de
nuestra aplicación.
INFRAESTRUCTURASDjango
53PYTHONW W W. L I N U X - M A G A Z I N E . E S W W W . L I N U X - M A G A Z I N E . E S
Figura 1: La interfaz de administración de Django. Figura 2: Para añadir información a nuestra aplicación personal.
01 from django.db import models
02
03 class Prueba(models.Model):
04 fecha = models.DateField(‘Date`)
05 entrada = models.CharField( max_length=`500` )
06
07 def __unicode__(self):
08 return ’%s %s` % (self.fecha, self.entrada)
Listado 1: miprueba/ Prueba/ models.py
01 from django.shortcuts import render_to_response
02 from models import Prueba
03
04 def todas_las_pruebas(request):
05 all_entries = Twit.objects.all().order_by(“fecha”).reverse()
06 return render_to_response(‘todas_las_pruebas.html’,{’entradas’:
todas_las_entradas })
Listado 2: Invertimos las Entradas
INFRAESTRUCTURAS Django
54 PYTHON W W W. L I N U X - M A G A Z I N E . E S
diferentes, podemos especificarlo en el
comando:
python manage.py U
runserver 127.0.0.1:5555
Suponiendo que lo hayamos ejecutado sin
dicha opción, nos dirigimos a http:// 127. 0. 0.
1:8000/ admin, donde se nos instará a iden-
tificarnos para acceder a la interfaz de admi-
nistración. Después de identificarnos, vere-
mos la pantalla que se muestra en la Figura
1.
Debido a que estamos haciendo una apli-
cación personal, podemos ignorar por ahora
las secciones Sites y admin y pulsar sobre el
icono Add del cuadro Prueba. Veremos
entonces algo como lo que se muestra en la
Figura 2.
Ya podemos introducir datos de entrada.
Al pulsar sobre Today se rellena automática-
mente la fecha actual, aunque podemos
usar el calendario para escoger otra. Lo
siguiente es introducir texto en Entrada y
pulsar sobre Save, que nos lleva a una
página que nos muestra todas las Pruebas de
nuestra base de datos. Al pulsar sobre la
entrada que acabamos de hacer, llegamos a
una interfaz de edición, con la que podre-
mos realizar cambios sobre ella o eliminarla.
Es posible compartir las entradas de la
web con otras personas. Para poder hacerlo
debemos añadir una vista (o módulo encar-
gado de la lógica) y una plantilla (la forma
en que se presentan los datos al usuario).
Aquellos que estén acostumbrados a otros
frameworks de tipo MVC, en los que la vista
suele ser la plantilla en sí misma, pueden
tener un poco más de dificultades a la hora
de acostumbrarse a las vistas de Django.
Para comenzar, editamos miprueba/
Prueba/ views.py, de forma que contenga un
simple método para devolver todas las
entradas en orden cronológico inverso (Lis-
tado 2). Así definimos el método TodasLas-
Pruebas, que recoge todos los
objetos Prueba, ordenados por el
campo de fecha, y los invierte.
Luego llama a render_to_res-
ponse(), con el nombre de la plan-
tilla para la vista y un diccionario
con los datos que le queremos
pasar.
Cuando hayamos acabado con
la vista, tendremos que hacer la
plantilla. El Listado 3 muestra un
ejemplo de marcado simple con el
que captar la idea general. Con el
fin de mantener las plantillas sepa-
radas de todo lo demás, guardamos el
archivo en miprueba/ plantillas/
todas_las_pruebas.html.
Como puede apreciarse, el lenguaje de
plantillas de Django es fácil de usar, aunque
tiene funcionalidades avanzadas. En ésta
usamos un bucle for simple, con el que ite-
ramos sobre los objetos Prueba que vamos a
pasar al método TodasLasPruebas() y mos-
trar los datos de cada objeto Prueba
mediante sus métodos fecha y entrada.
Luego configuramos Django para que
encuentre nuestra plantilla en el sistema de
archivos, e instalamos una URL que enlace
con la vista. Para instalar los directorios de
plantillas en miprueba/ settings.py hemos de
añadir la ruta completa a la lista TEM-
PLATE_DIRS, que dependerá del directorio
desde el cual se ejecutó startproject (debe-
mos asegurarnos de utilizar la ruta com-
pleta). Después editamos miprueba/ urls.py
para mapear la URL (Listado 4).
Así hemos importado las vistas específi-
cas de la aplicación, hemos añadido la URL
/pruebas/ y hemos dejado como estaba el
mapeo de la interfaz de administración. Si
nos dirigimos a http:// 127. 0. 0. 1:8000/
pruebas, veremos todas las Pruebas que
hemos añadido desde la interfaz de admi-
nistración.
El binomio formado por el servidor inde-
pendiente y SQLite es genial para el
desarrollo rápido, pero si lo que queremos
es montar una aplicación en producción es
mejor usar Apache con mod_python, junto
con una base de datos más robusta, como
PostgreSQL (ver el sitio web de Django).
Nuestro agradecimiento a Jacob Kaplan-
Moss y a Adrian Holovaty por su contribu-
ción a este artículo. Los listados de código se
pueden descargar de [7]. ■
Además, debemos crear un archivo
admin.py. En este ejemplo sólo usaremos la
configuración predeterminada, pero éste es
el lugar en el que se pueden personalizar
varios de los aspectos de la interfaz de admi-
nistración. miprueba/ Prueba/ admin.py
necesitará contener:
from django.contrib import admin
from miprueba.Prueba.models U
import Prueba
class PruebaAdminU
(admin.ModelAdmin):
pass
admin.site.registerU
(Prueba, PruebaAdmin)
Para verlo en acción, ejecutamos
python manage.py runserver
que arranca un servidor de pruebas en la
dirección http:// 127. 0. 0. 1:8000. Para ini-
ciarlo en una dirección IP o en un puerto[1] Sitio Web del proyecto Django:
http:// www. djangoproject. com
[2] Django Software Foundation: http://
www. djangoproject. com/ foundation
[3] Sitios desarrollados con Django:
http:// www. djangosites. org
[4] Descarga de Django: http:// www.
djangoproject. com/ download/
[5] Pysqlite2: http:// initd. org/ pub/
software/ pysqlite/
[6] Django Book:
http:// www. djangobook. com
[7] Código del Artículo:
http:// www. linux-magazine. es/
resources/ article_code
Recursos
01 <html>
02 <body>
03 <table>
04 <tr>
05 <th>Fecha</th>
06 <th>Entrada</th>
07 </tr>
08 {% for t in entradas %}
09 <tr>
10 <td>{{ t.fecha
}}</td>
11 <td>{{
t.entrada}}</td>
12 </tr>
13 {% endfor %}
14 </table>
15 </body>
16 </html>
Listado 3: Hacemos la Plantilla
01 from django.conf.urls.defaults import *
02 from django.contrib import admin
03 from mytwit import views
04
05 admin.autodiscover()
06
07 urlpatterns = Patterns(‘’,
08 (r’^twits/’.
’mytwit.Twit.views.alltwits’).
09 (r’admin/(.*)’, admin.site.root),
10 )
Listado 4: Mapear a URL
“La información quiere ser libre” es uno
de los lemas hacker, pero habría que pre-
guntarse cómo se moverá una vez que lo
sea. En un mundo como el actual, donde
la información y su procesado pueden
encontrarse distribuidos en decenas (¡o
miles!) de servidores, es preciso contar
con algún mecanismo que permita comu-
nicar sistemas informáticos de todo tipo
sin tener que recurrir a tecnologías de
bajo nivel. Este problema ha sido resuelto
por muchos mediante el empleo del pro-
tocolo HTTP, pero cuando lo que bus-
camos es rendimiento, HTTP puede no
ser una opción.
No es de extrañar que dos de las empre-
sas de referencia de Internet, Facebook y
Google, se hayan enfrentado a este mismo
problema y hayan creado, de forma total-
mente independiente, dos tecnologías que
buscan solucionarlo: Thrift [1] y Protocol
Buffers [2].
Tanto Facebook como Google necesita-
ban una forma de poder comunicar siste-
mas informáticos desarrollados en dife-
rentes lenguajes de programación para
que pudiesen intercambiar datos entre
ellos. Por más que queramos a nuestro
querido Python, cuando el rendimiento es
una prioridad, no es nuestra mejor
opción. Y puede ocurrir también lo con-
trario: por mucho que nos guste Java, en
numerosas ocasiones la velocidad de
desarrollo con Python ¡no tiene rival!
En este artículo echaremos un vistazo a
la tecnología Thrift de Facebook y vere-
mos cómo Python puede usarla para
comunicarse con un sistema Java que
está ganando gran popularidad: Elastic -
Search.
SerializandoAl acto de transformar un formato interno
de datos en algo que podamos transmitir
o almacenar de forma externa se le suele
denominar serializar. Existe una cantidad
inimaginable de formatos que podemos
usar para serializar datos, y Python viene
de serie con varias opciones:
• XML
• JSON
• Pickle
• Sqlite
Todos ellos cuentan con ventajas e incon-
venientes.
XML es uno de los formatos más exten-
didos del mundo. Al ser un formato de
texto es fácilmente manipulable, y es
posible editarlo a mano. Su nivel de com-
plejidad es seleccionable, podemos hacer
que sea tan complejo y almacene tanta
información como deseemos.
JSON fue la respuesta de la Web a la
complejidad de XML. Al igual que él, es
un formato de texto, pero su estructura
está cerrada. Es sorprendentemente sim-
ple y sencillo de manejar, lo que no
ayuda demasiado en situaciones comple-
jas.
Pickle es en realidad un mecanismo
propio de Python para la superlación de
objetos. Ningún otro lenguaje parece dis-
poner de soporte para él, y cuenta con al
menos dos versiones. Cualquier objeto
serializable en Python puede serializarse
con Pickle y recuperarse de nuevo intacto.
Aunque parezca una burrada, Sqlite
puede considerarse un formato de seriali-
zación. En principio no existe nada que
nos impida usar bases de datos Sqlite
como formato para transportar nuestros
datos entre aplicaciones. Será lento, pero
funcionará.
Todos estos formatos tienen algún 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-
zación es vital para el funcionamiento de
nuestros sistemas, necesitamos algo más
potente.
Instalación de Apache ThriftThrift es un protocolo de serialización
binario y tipado que define tanto datos
como servicios (ver Figura 1). Al ser bina-
rio es mucho más eficiente que los forma-
tos de texto, y puede enviar la misma can-
tidad de información de forma más com-
pacta, ahorrando ancho de banda. Ade-
más está tipado, lo que significa que toda
información que se transmita será de un
tipo de dato determinado. Esto es impres-
cindible si queremos ser capaces de inter-
cambiar datos con lenguajes tipados
como C#, Java o C++.
Podemos descargar el código fuente de
Thrift desde el enlace que aparece en el
Recurso 1. Para poder compilar Thrift
necesitaremos disponer de flex, así como
de la librería de desarrollo Boost en nues-
tro sistema (en Ubuntu necesitaremos el
paquete libboost-dev, por ejemplo). Una
vez la tengamos instalada deberemos des-
comprimir el fichero, compilarlo e insta-
larlo. Es recomendable ejecutar el confi-
gure indicando los lenguajes para los que
no queremos generar un compilador,
puesto que tratará de generar los compila-
dores de todos los lenguajes:
shell$ ./configure U
-without-javaU
--without--csharp
...
shell$ make
shell$ sudo make install
Podemos comprobar que se ha instalado
correctamente ejecutando el compilador
de Thrift:
INFRAESTRUCTURASThrift
55PYTHONW W W. L I N U X - M A G A Z I N E . E S
Benis
Ara
povic
- 123rf.c
om
Hacer que distintos servicios se comuniquen entre ellos es todo un problema que Facebook
ha tratado de solucionar con Thrift. Por José María Ruíz
Serialización estilo Web 2.0
INFRAESTRUCTURAS Thrift
56 PYTHON W W W. L I N U X - M A G A Z I N E . E S
shell$ thrift
Usage: thrift [options] file
Options:
-version Print the ?.
....
Necesitamos un componente más para
poder hacer uso de Thrift: la librería
python thrift. Instalarla es mucho más
sencillo:
shell$ pip install thrift
Ya tenemos todo lo necesario para usar
Thrift, pasemos a usarlo.
Hola MundoThrift funciona como un compilador
(Figura 2) que acepta una descripción de
los datos y servicios, la interfaz, que
vamos a utilizar, y genera a partir de ella
una librería en el lenguaje de destino que
codificará y decodificará ese formato en
el lenguaje de programación que le indi-
quemos. Es más sencillo verlo con un
ejemplo. Digamos que queremos dispo-
ner de un servicio llamado hola que
acepta una cadena de texto y devuelve
otra cadena de texto. Lo primero que
necesitaremos es crear un fichero de des-
cripción en el formato de Thrift como el
que aparece en el Listado 1. Una vez ten-
gamos el fichero listo, podemos generar
el código Python con el siguiente
comando:
shell$ thrift -gen pyU
hola.thrift
El comando generará un directorio lla-
mado gen-py que contendrá el módulo
que vamos a usar. Debemos copiar el
módulo hola al directorio en el que vaya-
mos a ejecutar tanto el script del Listado 2
como el del Listado 3. El código del Lis-
tado 2 genera un servidor de red que res-
ponderá a la interfaz declarada en el
fichero hola.thrift, mientras que en Lis-
tado 3 se encuentra el código que usará
este servicio. Para verlos en funciona-
miento tendremos que arrancar primero
el servidor en un terminal:
shell$ python servidor.py
Arrancando el servidor...
Y el cliente en otro terminal:
shell$ python cliente.py
Hola mundo
shell$
¡Ha funcionado! Thrift nos permite arran-
car un servidor de red con el servicio que
hemos definido. La librería se encarga de
prácticamente todo, lo que nos permite
concentrarnos en crear el código fuente
de nuestro servicio. Pero… ¿es Thrift
rápido? Hagamos una prueba. Con el ser-
vidor aún arrancado, vamos a mandar
Figura 1: Esquema de trabajo con Thrift.
01 service Saludos {
02 string hola(1: stringnombre)
03 }
Listado 1: Fichero hola.thrift
01 #!/usr/bin/env python0203 import sys04 05 from hola import Saludos06 from hola.ttypes import *07 08 from thrift.transport import
TSocket09 from thrift.server import TServer1011 ## Servicio12 class SaludosHandler:
13 def hola(self,nombre):14 return “Hola {0}”.format (nom-
bre)15 16 ## Pasos necesarios para arrancar17 ## el servidor18 handler = SaludosHandler()19 processor =
Saludos.Processor(handler)20 transport = TSocket.TServer-
Socket(9090)21 tfactory = TTransport.TBuffered-
TransportFactory()
22 pfactory = TBinaryProtocol. TBi-naryProtocolFactory()
2324 ## Arrancamos25 servidor = TServer.TSimple-
Server(processor, transport,tfactory, pfactory)
2627 print ‘Arrancando el servidor...’28 servidor.serve()29 print ‘acabamos.’
Listado 2: Fichero servidor.py
01 import sys02 from hola import Saludos03 from hola.ttypes import *04 from hola.constants import *0506 from thrift import Thrift07 from thrift.transport import
TSocket08 from thrift.transport import
TTransport09 from thrift.protocol import TBi-
naryProtocol
1011 try:12 transport = TSocket.TSocket
(‘localhost’, 9090)13 transport = TTransport.
TBufferedTransport(transport)14 protocol = TBinaryProtocol.TBi-
naryProtocol(transport)1516 cliente = Saludos.Client(proto-
col)17
18 ## Conectamos19 transport.open()2021 cadena = cliente.hola(“mundo”)22 print cadena2324 ## Cerramos la conexión25 transport.close()26 except Thrift.TException, tx:27 print “%s” % (tx.message)
Listado 3: Fichero cliente.py
10000 mensajes mediante el código del
Listado 4:
shell$ time python U
test_velocidad.py
real 0m1.715s
user 0m0.820s
sys 0m0.156s
No está nada mal, siendo Python, y sin
usar ninguna optimización. Podemos
parar el servidor pulsando la combina-
ción de teclas Control+C.
Cómo Funciona el Código FuenteAnalicemos el código del Listado 2. Carga-
mos el módulo hola resultado de nuestra
definición en el fichero hola.thrift, y que
se encontraba dentro del directorio gen-py.
Este módulo es en realidad el nombre del
propio fichero, que Thrift ha convertido en
módulo, por lo que hay que tener cuidado
con el nombre que demos al fichero .thrift.
Dentro del fichero hemos definido un ser-
vicio llamado Saludos. Thrift nos permite
reunir grupos de funciones y variables
bajo servicios. Así es muy sencillo organi-
zar nuestro código. Pasamos a cargar el
fichero ttypes que contiene todas las fun-
ciones, objetos y variables que necesitare-
mos para usar Thrift.
Nuestro servicio será un objeto Python
con métodos que tengan los mismos
nombres y parámetros que definimos en
el fichero hola.thrift. Por convención se
añade la palabra Handler al nombre del
servicio que vamos a implementar, en
nuestro caso SaludosHandler.
Mediante el método Saludos.Procesor(),
indicamos qué objeto implementará la
interfaz definida. Al objeto resultante se
le suele llamar processor, puesto que su
función será procesar peticiones.
En este punto podemos elegir cómo
vamos a usar nuestro procesador. Thrift
debe trabajar sobre algún protocolo deco-
municaciones, ofreciendo varias posibili-
dades dependiendo del lenguaje de pro-
gramación que usemos. En Python es
posible usar un socket, el protocolo http o
Twisted. Por simplicidad vamos a usar un
socket, que es el protocolo de más bajo
nivel, mediante la clase TSocket. Sobre el
protocolo de comunicaciones debemos
montar un servidor, TServer, que atienda
los mensajes que lleguen y se los pase al
procesador. Como puedes ver, los nom-
bres son bastante descriptivos.
Thrift nos obliga aún a hacer algunas
elecciones. Debemos indicar al servidor
qué clase de protocolo vamos a usar – en
nuestro caso TBinaryProtocol – y cómo
queremos que se comporte el servidor,
usando un búfer con TBufferedTransport.
Thrift es configurable y nos ofrece dife-
rentes opciones para casi todo. Podríamos
haber seleccionado un protocolo basado
en JSON mediante TJSONProtocol, por
ejemplo.
Ya sólo nos falta arrancar el servidor,
instanciando por ejemplo TSimpleServer y
llamando al método server() que se blo-
queará mientras no lleguen mensajes.
ElasticSearchComo ejemplo del uso de Thrift vamos a
crear un pequeño programa Python que
emplee esta tecnología para interactuar
con el motor de búsqueda de moda: Elas-
ticSearch [3]. ElasticSearch está revolu-
cionando el mundo de los motores de
búsqueda. Ofreciendo el rendimiento de
Solr/ Lucene, pero añadiendo la capacidad
de trabajar de forma distribuida, reparte
el índice de búsqueda entre varias máqui-
nas de forma automática. Está progra-
mado en Java y ofrece varios protocolos
de trabajo, siendo posible comunicarse
con el servidor ElasticSearch mediante
Rest sobre http o Thrift (Figura 3).
Para instalar ElasticSearch sólo tene-
mos que descargarlo desde la dirección
del Recurso 3 :
shell$ wget -c U
https://github.com/downloads/U
elasticsearch/elasticsearch/U
elasticsearch-0.16.2.tar.gz
shell$ tar zxpf U
elasticsearch-0.16.2.tar.gz
shell$ cd elasticsearch-0.16.2
shell$ cd bin
shell$ ./plugin -install U
transport-thrift
shell$ ./elasticsearch -f
¡Listo! Ya tenemos funcionando un motor
de búsqueda con índice distribuido y que
se comunica usando Thrift. Es normal
que ElasticSearch se esté ganando el cora-
zón de muchos desarrolladores. Debemos
generar el módulo de la interfaz de Thrift
para Python. Para ello debemos descargar
el fichero elasticsearch.thrift de la direc-
ción que aparece en el Recurso 4. Y com-
pilarlo:
shell$ thirft --gen U
py elasticsearch.thrift
Cuando tengamos el directorio gen-py,
extraemos de su interior el módulo elas-
INFRAESTRUCTURASThrift
57PYTHONW W W. L I N U X - M A G A Z I N E . E S
Figura 2: Http vs Thrift.
01 import sys02 sys.path.append(‘./gen-py’)03 04 from hola import Saludos05 from hola.ttypes import *06 from hola.constants import *0708 from thrift import Thrift09 from thrift.transport import
TSocket10 from thrift.transport import
TTransport11 from thrift.protocol import TBi-
naryProtocol1213 transport =
TSocket.TSocket(‘localhost’,9090)
14 transport =TTransport.TBufferedTransport(transport)
15 protocol = TBinaryProtocol.TBi-
naryProtocol(transport)1617 cliente = Saludos.Client(proto-
col)1819 transport.open()20 21 for i in range(0,10000):22 cadena = cliente.hola(“mundo”)2324 transport.close()
Listado 4: Test de Velocidad
• Método (POST, GET, PUT…)
• URI(<indice>/ <modelo>/ <id>….)
• Cabeceras (Headers)
• Cuerpo del mensaje (Body)
Algunos métodos exigen el uso del body
(por ejemplo los que requieren el método
POST), mientras que otros sólo requieren
el URI (GET) ¿Y por qué usamos números
para el tipo de método usado? Si echamos
un vistazo al fichero elasticsearch.thrift
veremos que ahí se declaran los números
que usaremos para los métodos.
En nuestro ejemplo podemos ver dife-
rentes métodos en uso. Crear un índice
exige un POST, añadir un modelo, un
PUT y hacer una consulta, un GET. Cada
llamada de ejecución de un request
devuelve un objeto RestResponse con un
campo body, en el que encontraremos el
resultado codificado en JSON.
ConclusiónThrift puede parecer algo complejo ahora
que todos nos hemos acostumbrado a
emplear HTTP como protocolo para las
peticiones remotas. Pero existen muchas
situaciones en las que necesitaremos uti-
lizar un protocolo que consuma menos
ancho de banda y ofrezca más rendi-
miento. Tanto Facebook como Google
han tenido que desarrollar su propia tec-
nología para solventar este problema, y
ambos han tenido la gentileza de libe-
rarla como software libre. Y por si fuese
poco, ambos sistemas generan código
Python, todo un regalo para nuestra
comunidad. ■
ticsearch y lo ubicamos en el mismo direc-
torio en el que pongamos el script del Lis-
tado 5. Cuando lo ejecutemos, éste será el
resultado:
shell$ time python busqueda.py
[{u’_score’: 0.38431653, U
u’_type’: u’articulo’, U
u’_id’: u’1’,
u’_source’: {u’titulo’: U
u’Thrift, Python y U
ElasticSearch’},
u’_index’: u’linuxmagazine’}]
real 0m0.353s
user 0m0.040s
sys 0m0.016s
Hemos creado un índice, añadido un
modelo de documento, insertado un
documento y realizado 100 búsquedas en
300 milisegundos. ElasticSearch trabaja
usando una API Rest que acepta coman-
dos codificados en URIs (rutas) mediante
los métodos típicos de HTTP. Tanto los
datos enviados como los recibidos se
codifican en JSON, que podemos codifi-
car y decodificar empleando la librería
json de Python.
El esquema de trabajo es parecido al
que hemos visto con anterioridad. Crea-
mos una conexión usando TSocket, espe-
cificamos el tipo de transporte y el proto-
colo (en este caso una variante del bina-
rio) y generamos un cliente.
La clase RestRequest ha sido generada
por Thrift y tiene cuatro parámetros:
INFRAESTRUCTURAS Thrift
58 PYTHON W W W. L I N U X - M A G A Z I N E . E S
[1] Tecnologías Thrift de Facebook:
http:// thrift. apache. org/
[2] Tecnología Protocol Buffers de Goo-
gle:
http:// code. google. com/ p/ protobuf/
[3] Motor de búsqueda ElasticSearch:
http:// www. elasticsearch. org/
[4] Fichero thrift para ElasticSearch:
https:// github. com/ elasticsearch/ elas-
ticsearch/ blob/ master/ plugins/ trans-
port/ thrift/ elasticsearch. thrift
Recursos
01 from thrift import Thrift02 from thrift.transport import
TTransport03 from thrift.transport import
TSocket04 from thrift.protocol.TBinaryPro-
tocol import TBinaryProtocolAc-celerated
0506 from elasticsearch import Rest07 from elasticsearch.ttypes import
*08 09 import json1011 socket = TSocket.TSocket(“local-
host”, 9500)12 transport =
TTransport.TBufferedTransport(socket)
13 protocol = TBinaryProtocolAccel-erated(transport)
14 client = Rest.Client(protocol)15
16 transport.open()1718 ## Creamos un índice19 request = RestRequest(method=1,
uri=”/linuxmagazine”,20 headers={}, body=”“)21 client.execute(request)2223 ## Cargamos un modelo de documento24 mapping = json.dumps({‘proper-
ties’: {25 ‘titulo’ : {‘type’ : ‘string’,
‘store’ : ‘yes’}}})26 request = RestRequest(method=2,
uri=”/linuxmagazine/articulo”,27 headers={}, body= mapping)28 client.execute(request)2930 ## Cargamos un documento31 articulo = json.dumps({‘titulo’ :
‘Thrift, Python y Elastic-Search’})
32 request = RestRequest(method=2,33 uri=’/linuxmagazine/artic-
ulo/1’,34 headers={},35 body= articulo)36 respuesta =
client.execute(request)3738 ## Buscamos la cadena thrift39 ruta = “/linuxmagazine/artic-
ulo/_search?q=thrift”40 for i in range(0, 100):41 request = RestRequest(method=0,42 uri=ruta,43 headers={},44 body= ‘’)45 respuesta =
client.execute(request)4647 print
json.loads(respuesta.body)[“hits”][“hits”]
48 49 transport.close()
Listado 5: Interactuando con ElasticSearch
Figura 3: Funcionamiento de ElasticSearch.
Cuaderno de bitácora, fecha estelar
2123….
En todos los libros sobre administra-
ción de sistemas se nos recomienda llevar
un pequeño cuaderno de bitácora donde
ir reflejando las acciones peligrosas que
realicemos. De esta manera, se supone,
podremos recrear paso a paso los eventos
que nos llevaron a un desastre y por tanto
ir deshaciéndolos en orden inverso.
La cruda realidad es que no todo el
mundo usa dichos cuadernos. Es pesado
tener que dejar el teclado y coger el bolí-
grafo para escribir… ¡a mano! ¿no estába-
mos en la era de los ordenadores? ¿No
íbamos a desterrar el papel?
Muchas personas usan un weblog en su
propia máquina o en Internet para ir
apuntando detalles o noticias que le resul-
tan de interés. Mucha gente incluso publi-
ca sus ficheros de configuración, de
manera que siempre pueda acceder a
ellos.
¿Y que ocurre si solo lo queremos para
nosotros? ¿Y si la máquina a la que esta-
mos accediendo no tiene un servidor web
con el software adecuado configurado
para tener un weblog? ¿y si no queremos
montar tanta parafernalia?
Algunas aplicaciones, como KPIM,
incorporan ya la opción de llevar un dia-
rio personal, pero no funcionan de forma
remota a no ser que tengamos una cone-
xión de red con mucho ancho de banda.
¿Qué opciones nos quedan? Podemos
volver nuestra mirada a la era antigua de
los ordenadores, cuando los interfaces
funcionaban exclusivamente desde una
consola de texto. Dichos interfaces aún se
utilizan en numerosas aplicaciones, la
razón es que son mucho más simples de
usar. Es más fácil automatizar el pulsar
tres veces TAB que mover el ratón y fun-
cionan mejor remotamente, aún con
conexiones lentas.
Vamos a diseñar y programar un cua-
derno de bitácora en Python, que utilizará
ncurses para el interfaz texto y dbm para
almacenar las entradas por fecha.
Diseño del CuadernoComencemos nuestro diseño echando un
vistazo a las librerías en que nos vamos a
basar. ncurses fue desarrollada para abs-
traer, ocultar y simplificar la gestión de
terminales texto. Cada fabricante dotaba
a su terminal de texto de características
LIBRERÍASCurses
59PYTHONW W W. L I N U X - M A G A Z I N E . E S
La librería curses en Python
Cuaderno deBitácora
¿Te acuerdas de cuando cambiaste la versión 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 bitácora. Por José María Ruíz y Pedro Orantes
LIBRERÍAS Curses
60 PYTHON W W W. L I N U X - M A G A Z I N E . E S
distintas a las del resto, forzadas la mayo-
ría de las veces por una feroz competen-
cia. Esto convertía en una tortura el sim-
ple hecho de cambiar un terminal por
otro, requiriendo la mayoría de las veces
la modificación del programa de turno.
ncurses permitía realizar programas sin
tener en cuenta las diferencias entre los
terminales. No solo eso, sino que además
simplificó enormemente la gestión de
interfaces de texto como veremos más
adelante.
dbm es una “base de datos”. Lo pongo
entre comillas porque en realidad sólo
nos permite almacenar datos, recuperar-
los y realizar búsquedas, pero no usando
SQL sino llamadas a librerías. <dbm> es
una familia de librerías que nos permiten
almacenar datos en un fichero y gestio-
narlos como si fuesen un diccionario o
hash en Python. Cada entrada se compo-
ne de una clave y un valor asociado. Si no
tenemos que realizar búsquedas comple-
jas, dbm se convertirá en nuestro mejor
opción.
Básicamente 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.
Las acciones serán:
• Navegar entradas.
• Crear entrada.
• Editar entrada.
• Borrar entrada.
• Salir.
Cada una de las acciones se corresponde-
rá con una combinación de teclas.
Comenzaremos creando los objetos que
gestionen los datos y posteriormente el
interfaz con el usuario.
Almacenamiento de datosDebemos conservar el texto asociado a
una fecha y hora en algún sitio. Con la
fiebre actual por las bases de datos rela-
cionales pocas veces se menciona la
existencia otras bases de datos que no
cumplen el estándar relacional ni SQL.
¿Realmente se necesita un motor rela-
cional y SQL para cualquier cosa que
necesitemos almacenar? Por supuesto
que no. Desgraciadamente, cuando sólo
tienes un martillo, todo te parece cla-
vos.
El problema está en la definición de
“base de datos”, dbm lo es pero sin
mucha sofisticación. Básicamente nos
permite almacenar claves y valores aso-
ciados a las mismas, así como recuperar
el valor o borrar las claves. Ni más, ni
menos.
La librería dbm necesita un fichero
donde depositar los datos que se alma-
cenan. Así, tendremos que darle el
nombre de un fichero e indicarle como
queremos que lo trate. Puede abrir el
fichero para introducir nuevos datos o
crearlo de nuevo, aunque ya exista uno
con el mismo nombre.
Una vez abierto el fichero, un objeto
dbm se comporta como un contenedor
cualquiera. Podremos hacer uso de la
sintaxis “[]” a la que nos tienen acos-
tumbrados la mayor parte de los lengua-
jes de programación.
Como podemos observar en el Listado
1, el uso de la librería dbm es realmente
simple. Se comporta como una lista,
con todas sus operaciones. El lector se
habrá preguntado al ver el código:
“¿Dónde está el truco? si dbm represen-
01 >>> import dbm02 >>> datos = dbm.open(‘visi-
tantes’,’c’) # crea el fichero03 >>> datos[“Juan Jose”] = “vendra
el martes”04 >>> datos[“Juan Jose”]05 ‘vendra el martes’
06 >>> datos.close()07 >>>08 >>> datos = dbm.open(‘visi-
tantes’)09 >>> datos[“Juan Jose”]10 ‘vendra el martes’11 >>> datos.keys()
12 [‘Juan Jose’]13 >>> for llave in datos.keys():14 ... print “[“+llave+”] -> “ +
datos[llave]15 ...16 [Juan José] -> vendra el martes17 >>> datos.close()
Listado 1: Ejemplo de uso de DBM
01 #!/usr/local/bin/python02 03 #!/usr/local/bin/python04 05 import dbm06 class Almacen:07 def __init__ (self,nombre):08 self.bd = dbm.open(nombre,’c’)09 10 def busca_palabra (self, pal-
abra):11 claves = self.entradas()12 encontradas = []13 14 for clave in claves:
15 contenido =self.contenido(clave)
16 if palabra in contenido:17 encontradas.push(clave)18 19 return encontradas20 21 def entradas (self):22 a = self.bd.keys()23 if not a:24 a = []25 return a26 27 def cierra (self):28 self.bd.close()
29 30 def __len__(self):31 return len(self.entradas())32 33 def __setitem__ (self, clave,
valor):34 self.bd[clave] = valor35 36 def __getitem__(self,clave):37 return self.bd[clave]38 39 def __delitem__(self,clave):40 del self.bd[clave]
Listado 2: almacen.py
Figura 1: Hola Mundo en nuestro primer pro-
grama curses.
Figura 2: Un cuadro de texto curses.
LIBRERÍASCurses
61PYTHONW W W. L I N U X - M A G A Z I N E . E S
ta una base de datos ¿por qué puede
hacer uso de la sintaxis []?”.
La respuesta es que en Python la sinta-
xis “[]” es lo que en inglés se llama
“syntactic sugar”. Por traducirlo de alguna
manera, viene a decir que es una manera
de hacer agradable visualmente (y a
nuestro pobres dedos) la llamada a ciertas
funciones del lenguaje.
¿Podemos incorporar “[]” a uno de
nuestro objetos y hacer que se comporte
como una lista? La respuesta es: ¡Sí! y no
tiene nada de complicado.
Python reserva unas serie de métodos
debido a su uso especial, entre ellos
están:
• def __len__(self)
• def __setitem__(self, clave, valor)
• def __getitem__(self, clave)
• def __delitem__(self, clave)
Estos cuatro métodos los enmascara
python posteriormente de la manera mos-
trada en la Tabla 1. Por tanto podemos
enmascarar las acciones de un objeto de
manera que se use como si fuese un dic-
cionario. Y precisamente eso es lo que
hacemos con nuestro objeto Almacen que
encubre un diccionario, añadiendo nue-
vas acciones. El lector puede comprobar
el código en el Listado 2 (disponible en
[1]).
CursesCurses son unas librerías de bajo nivel.
Las abstracciones que crean son muy
básicas: preparar consola, crear “venta-
nas” (nada que ver con las gráficas),
escribir en esas ventanas, recoger caracte-
res y poco más.
Debido a ello son bastante complicadas
de manejar. Hacer cosas vistosas suele lle-
var mucho código. Por ello nos vamos a
centrar en un interfaz sencillo. Nuestro
programa será modal, tendrá un modo de
“navegación” y uno de “edición”, al igual
que el editor “Vi”. Precisamente “Vi” fue
uno de sus primeros usuarios.
Diseño PrincipalComenzaremos por inicializar curses. Por
desgracia, esto también nos hace perder
el control de nuestra consola Python,
puesto que anula su funcionamiento. Por
ello se pide al lector que ejecute todas las
acciones relacionadas con curses desde
un programa Python ejecutable (recuerda
hacer el chmod +x <programa>).
Podemos ver un programa que inicializa
la consola con curses en el Listado 3.
Posteriormente escribimos un “Hola
mundo” y refrescamos la pantalla, pode-
mos ver el resultado en la Figura 1. Esta
parte es vital, si no refrescamos la panta-
lla curses no mostrará nada. En el Listado
3 stdscr representa toda la pantalla. Es
posible crear subventanas y hacer actuali-
zaciones selectivas como podremos com-
probar en el código del programa.
Una vez realizadas las operaciones,
pasamos a dejar la pantalla en una
configuración correcta, acción que reali-
zan las cuatro últimas llamadas a funcio-
nes.
El objeto diario creará a su vez un obje-
to GUI, que gestiona el interfaz, y el obje-
to Almacen que se encarga de gestionar la
base de datos.
01#!/usr/local/bin/python02 # -*- coding: ISO8859-1 -*-0304 import curses05 06 # Inicializamos la pantalla07 stdscr=curses.initscr()
08 curses.noecho()09 curses.cbreak()10 stdscr.keypad(1)11 12 # Escribimos algo13 stdscr.addstr(“Hola mundo”,0)14 stdscr.refresh()
15 16 # Limpiamos la pantalla17 stdscr.keypad(0)18 curses.echo()19 curses.nocbreak()20 curses.endwin()
Listado 3: “Hola mundo” con curses
01#!/usr/local/bin/python02 # -*- coding: ISO8859-1 -*-03 04 import curses05 import curses.textpad06 07 ncols, nlines = 9, 4
08 uly, ulx = 15, 2009 stdscr.addstr(uly-2, ulx, “Use
Ctrl-G to end editing.”)10 win = curses.newwin(nlines,
ncols, uly, ulx)11 rectangle(stdscr, uly-1, ulx-1,
uly + nlines, ulx + ncols)
12 stdscr.refresh()13 return Textbox(win).edit()14 15 str =
curses.wrapper(test_editbox)16 print ‘Contents of text box:’,
repr(str)
Listado 4: Ejemplo de uso de Textbox
01def ejecuta_commando(self, ch):02 “Procesa las teclas recibidas”03 if curses.ascii.isprint(ch):04 for comando in self.comandos:05 if comando[0] == chr(ch):06 (getattr(self,comando[1]))()
07 break08 else:09 if ch in (curses.ascii.DLE,
curses.KEY_UP):10 self.incr_pos_fechas()11 self.redibuja()
12 elif ch in (curses.ascii.SO,curses.KEY_DOWN):
13 self.decr_pos_fechas()14 self.redibuja()15 self.refresca()16 return 1
Listado 5: Método ejecuta_comando(self,ch)
Figura 3: Inserción de una nueva entrada en
la bitácora.
Figura 4: Vista de la bitácora con varias
entradas.
LIBRERÍAS Curses
62 PYTHON W W W. L I N U X - M A G A Z I N E . E S
001 #!/usr/local/bin/python002 # -*- coding: ISO8859-1 -*-003 004 import curses005 import curses.ascii006 import curses.textpad007 import time008 import os.path009 import string010 import sys011 import almacen012 013 class GUI:014 “”“Interfaz con el usuario.”“”015 016 def __init__(self,datos):017018 self.registra_comandos()019020 self.datos = datos021 self.pos_fechas =
len(self.datos) - 1022 023 self.genera_ventanas()024 025 self.banner(“-- [n] nueva | [e]
editar | [q] salir --“)026 027 self.dibuja_fechas()028 029 self.refresca()030 031 def genera_ventanas(self):032 “Genera las ventanas iniciales”033 self.scr_fechas = stdscr.
subwin(23, 80, 0, 0)034 self.scr_info = stdscr.
subwin(1,80,23,0)035 036 037 def registra_comandos(self):038 “Almacena la letra y el comando
asociado”039 self.comandos = [[‘d’,’bor-
rar’],040 [‘e’,’editar’],041 [‘n’,’nueva_entrada’],042 [‘q’,’salir’],043 [‘s’,”estado”]]044 045 def ejecuta_comando(self, ch):046 “Procesa las teclas recibidas”047 if curses.ascii.isprint(ch):048 for comando in self.comandos:049 if comando[0] == chr(ch):050 (getattr(self,comando[1]))()051 break052 else:053 if ch in (curses.ascii.DLE,
curses.KEY_UP):054 self.incr_pos_fechas()055 self.redibuja()056 elif ch in (curses.ascii.SO,
curses.KEY_DOWN):
057 self.decr_pos_fechas()058 self.redibuja()059 060 self.refresca()061 return 1062 063 def fecha(self):064 return time.strftime(“%Y-%m-%d
%H:%M”)065 066 def long_fecha(self):067 caja_texto = 2 # el | izquierdo
y el | derecho068 return len(self.fecha()) +
caja_texto069 070 def long_linea_texto(self):071 return (80 - self.long_fecha())072 073 def banner(self, texto):074 “Muestra el texto en la zona de
banner”075 self.scr_info.clear()076 self.scr_info.addstr(texto,
curses.A_BOLD)077 self.scr_info.refresh()078 079 def dibuja_fechas(self):080 “Genera el listado de fechas de
la izquierda”081 082 self.scr_fechas.clear()083 pos_x = 3084 085 # 8 elementos por arriba y abajo086 min = self.pos_fechas - 8087 max = self.pos_fechas + 8088 089 if max > len(self.datos):090 max = len(self.datos)091 min = min + (max -
len(self.datos))092 if min < 0:093 max = max + ( -min)094 min = 0095 096 if len(self.datos) > 0:097 # Marcamos con negrita la fecha
sobre la que está el curso098 # para ello iteramos escribi-
endo las fechas y cuando la099 # encontramos le pasamos el
atributo de negrita100 fechas = self.listado_fechas()101 for fecha in fechas[min:max]:102 103 fecha_temp = “[“+fecha+”] “104 105 if fechas[self.pos_fechas] ==
fecha:106 # Hemos encontrado nuestra
fecha!!!107 self.scr_fechas.addstr
(pos_x,1,fecha_temp ,curses.A_BOLD | curses.A_UNDERLINE)
108 109 else:110 self.scr_fechas.addstr
(pos_x,1,fecha_temp, 0)111 112 self.scr_fechas.addstr
(pos_x,len(fecha_temp),self.datos[fecha],0)
113 pos_x = pos_x + 1114 115 self.refresca()116 117 def editar(self):118 “Muestra un cuadro para
introducir el texto y lo guarda”119 if len(self.datos) == 0:120 return121 else:122 self.banner(“¡ EDITANDO ! --
[control+g] guardar | [q] salir--“)
123 fechas = self.listado_fechas()124 texto = self.datos
[fechas[self.pos_fechas]]125 self.refresca()126 127 # Capturamos el nuevo texto.128 129 ncols, nlineas = 25, 4130 uly, ulx = 15, 20131 stdscr.addstr(uly-2, ulx, “Usa
Ctrl-G para guardar.”)132 win = curses.newwin(nlineas,
ncols, uly, ulx)133 curses.textpad.rectangle
(stdscr, uly-1, ulx-1, uly +nlineas, ulx + ncols)
134 stdscr.refresh()135 nuevo_texto=
curses.textpad.Textbox(win).edit()
136 stdscr.refresh()137 138 nuevo_texto =
nuevo_texto.replace(‘\n’,’’)139 # Guardamos el nuevo texto140 self.datos[fechas
[self.pos_fechas]] =nuevo_texto
141142 self.banner(“-- [n] nueva | [e]
editar | [q] salir --“)143 144 def borrar(self):145 “Elimina la entrada selec-
cionada”146 if len(self.datos) > 0:147 fechas = self.listado_fechas()148 del self.datos
[fechas[self.pos_fechas]]149 self.actualiza_pos_fechas()150 self.redibuja()151 self.refresca()152153 def redibuja(self):154 “Redibuja la pantalla”155 self.dibuja_fechas()
Listado 6: cuaderno.py
El objeto Almacen es pasado a GUI
como parámetro en su creación. Y la
misión de GUI no es otra que al de res-
ponder a los eventos que el usuario envíe
mediante un bucle infinito.
Dos ventanasNuestro programa va a disponer de dos
ventanas. La mayor hará las veces de
“tablón” donde podemos ver las anota-
ciones realizadas por el momento.
Podremos desplazarnos arriba y abajo por
él. Para indicar qué fecha es la que tene-
mos seleccionada la distinguiremos ilumi-
nándola en negrita y subrayándola.
La segunda venta hará las veces de
barra de ayuda y estado. Cuando cambie-
mos el estado, por ejemplo al editar, se
reflejará ahí. Es el mismo modo de trabajo
del que hace gala VIM.
Las ventanas deben partir la pantalla de
manera que no se solapen. La pantalla de
un terminal tiene 80 columnas de ancho y
25 filas de alto. Dejaremos una fila abajo,
que será la que usemos para mostrar
información. La 24 filas restantes se
encargarán de mostrar las entradas alma-
cenadas.
Desplazamiento por las entradasLa ventana de datos nos permitirá des-
plazarnos arriba y abajo por las entra-
das. ¿Cómo podemos conseguir recrear
este movimiento? La solución es captu-
rando las teclas de los cursores “arriba”
y “abajo”. Cuando una de ellas se pulse
incrementaremos o decrementaremos
una variable que establece la posición
la entrada seleccionada en cada
momento y volveremos a dibujar, o
escribir, la pantalla de datos. Pero no lo
haremos de cualquier forma.
Queremos que el efecto sea vistoso, así
que siempre intentaremos mostrar un
número fijo de entradas encima y debajo
de la nuestra. Como tenemos la posición
de la entrada seleccionada, o resaltada,
con un sencillo cálculo podemos selec-
cionar que entradas mostraremos.
Las listas en Python tienen una funcio-
nalidad que nos será muy útil. Usando la
sintaxis lista[comienzo:fin] podemos
extraer los elementos entre comienzo y
fin formando una nueva lista.
Simplemente tenemos que seleccionar
aquellos que estén a una distancia fija
del seleccionado y usarlos como comien-
zo y fin.
Podemos ver el código que realiza esta
acción en el método dibuja_fechas(self)
del GUI en el Listado 6.
TextboxPython provee de una herramienta muy
útil para la edición de textos dentro de
curses. Desgraciadamente, a pesar de su
LIBRERÍASCurses
63PYTHONW W W. L I N U X - M A G A Z I N E . E S
156 self.refresca()157 158 def refresca(self):159 self.scr_fechas.refresh()160 161 def listado_fechas(self):162 “Devuelve el listado de fechas
ordenado”163 fechas = self.datos.entradas()164 fechas.sort(reverse=-1)165 return fechas166 167 def decr_pos_fechas(self):168 “Mueve la fecha seleccionada
hacia arriba”169 if self.pos_fechas >=
(len(self.datos) - 1):170 self.pos_fechas =
len(self.datos) - 1171 else:172 self.pos_fechas += 1173 174 def incr_pos_fechas(self):175 “Mueve la fecha seleccionada
hacia abajo”176 if self.pos_fechas <= 0:177 self.pos_fechas = 0178 else:179 self.pos_fechas -= 1180 181 def actualiza_pos_fechas
(self,fecha=”“):182 “Realiza los cambios oportunos
cuando se elimina una fecha”183 if fecha == “”:184 self.decr_pos_fechas()185 else:186 fechas = self.listado_fechas()187 cont = 0
188 resultado = 0189 for f in fechas:190 if f == fecha:191 resultado = cont192 break193 cont += 1194 195 self.pos_fechas = resultado196 197 self.redibuja()198 self.refresca()199 200 def nueva_entrada(self):201 “Introduce una nueva fecha”202 fechas = self.listado_fechas()203 fecha = self.fecha()204 if not fecha in fechas:205 self.datos[fecha] = “Texto
vacio”206 self.actualiza_pos_fechas 207 self.redibuja()208 self.refresca()209 self.editar()210 211 def salir(self):212 cierra_curses(stdscr)213 sys.exit(0)214 215 def estado(self):216 “Muestra información general”217 cadena = “”218 fechas = self.listado_fechas()219 cont = 0220 for fecha in fechas:221 cadena +=
“[“+str(cont)+”>”+fecha+”]”222 cont += 1223
224 cadena =“[P:”+str(self.pos_fechas)+”]”+”--[L:”+str(len(self.datos))+”]” + cadena
225 self.banner(cadena)226 227 class Diario:228 def __init__(self):229 self.datos = almacen.Alma-
cen(‘diario’)230 self.gui = GUI(self.datos)231 self.bucle_infinito()232 233 def bucle_infinito(self):234 while 1:235 c = stdscr.getch()236 n = self.gui.ejecuta_comando
(c)237 if not n:238 break239 240 def arranca_curses(stdscr):241 curses.noecho()242 curses.cbreak()243 stdscr.keypad(1)244 245 def cierra_curses(stdscr):246 stdscr.keypad(0)247 curses.echo()248 curses.nocbreak()249 curses.endwin()250 251 if __name__==’__main__’:252 stdscr=curses.initscr()253 arranca_curses(stdscr)254 diario = Diario()255 cierra_curses(stdscr)
Listado 6: cuaderno.py (cont.)
tidad de ifs anidados, cada uno de los
cuales responde ante una tecla o combi-
nación distinta. El código generado llegar
a convertirse en ilegible en cuanto el
número de comandos sobrepasa los diez.
Hay una manera mucho más elegante
de atacar este problema, pero no es tan
fácil hacer uso de ella en todos los lengua-
jes. Afortunadamente Python nos permite
una implementación muy sencilla. La
idea es la siguiente: Cada comando estará
asociado a una serie de acciones a reali-
zar. Englobaremos las acciones vincula-
das con cada comando a un método de
nuestro objeto “GUI”. Hasta aquí todo es
bastante normal. Ahora viene la magia.
Python nos permite invocar métodos de
objetos usando su nombre. Si declaramos
el objeto persona:
>>> class Persona:
... def habla(self):
... print "hola mundo"
...
>>>
Podemos invocar el método habla usando
una cadena con su nombre mediante el
método getattr(), que precisa de la instan-
cia del objeto y el método a invocar.
Devuelve, por así decirlo, una referencia
al método en cuestión que funciona de la
misma manera. Como dicen por ahí, “una
imagen vale más que mil palabras”:
>>> pepe = Persona()
>>> pepe.habla()
hola mundo
>>> (getattr(pepe, "habla"))()
hola mundo
>>>
Lo que haremos será crear una lista de lis-
tas, cada una de las cuales contendrá dos
elementos. El primero será un carácter y
el segundo el nombre del método a invo-
car. De esta manera, nuestro gestor de
comandos se reduce a un código que reci-
be un carácter, lo compara con el primero
elemento de cada entrada en su lista de
comandos y si encuentra una coinciden-
cia ejecuta el comando asociado. Ver
Listado 5.
Como podemos ver en el código, se
comprueba si el carácter recibido es
“imprimible” y posteriormente se busca en
la lista de comandos. En caso de coinci-
dencia se ejecuta usando como instancia
self. De esta manera, es posible manipular
el funcionamiento ante qué caracteres res-
ponde el programa sin tener que modificar
el código fuente. Esto nos da mucha flexi-
bilidad y es menos propenso a errores.
Uso del ProgramaEl uso del programa se ha hecho lo más
simple posible, el aspecto del mismo se
puede ver en la Figura 4. Cuando se pulsa
“n” se crea una nueva entrada con la
fecha y la hora, si existe ya una entrada
con la fecha y la hora no se hace nada.
Con “d” se elimina una entrada y con “e”
se edita. Cuando se está introduciendo un
texto, al pulsar “control+g” se guarda.
Para salir se pulsa “q” y con los cursores
“arriba” y “abajo” nos desplazamos por la
lista.
Al fichero de almacenado se le ha dado
el nombre “diario.db”. Si no existe se
crea, y si existe se emplea el existente.
ConclusiónAunque el uso de Curses puede resultar
engorroso, Python nos provee de una
librería que las manipula dentro su insta-
lación base. Una vez realizado el progra-
ma sabremos que cualquiera que instale
Python podrá hacer uso de él.
Siempre es posible realizar una serie de
objetos que realicen tareas de más alto
nivel. Existen librerías que nos proporcio-
nan barras de menús y widgets más avan-
zados. Aún así, siempre es bueno estar lo
más cerca posible del estándar.
La próxima vez que tengas que hacer
un interfaz en modo texto puede que sea
una buena idea darle una oportunidad a
curses. ■
potencia posee algunos inconvenientes de
los que hablaremos más tarde.
Esta herramienta es el objeto Textbox
que se encuentra en la librería curses.text-
pad. Textbox nos permite editar un texto
dentro de una ventana y poder utilizar
muchas de las combinaciones de teclas
que soporta EMACS. Así por ejemplo con
“control+e” iremos al final de la linea
que estemos editando y con “control+d”
borraremos el carácter sobre el que nos
encontremos.
Utilizaremos un Textbox para recoger el
texto que el usuario quiera introducir.
Desgraciadamente posee una limitación
debido su diseño: Si cuando estamos edi-
tando el texto, pulsamos repetidas veces
el cursor izquierdo desplazándonos hasta
dar con el borde de la ventana, el progra-
ma fallará.
El soporte de curses de Python se basa
en las librerías originales escritas en C, y
como ya hemos dicho son de muy bajo
nivel. La implementación de Textbox es
realmente básica y no controla todas las
circunstancias. Aún así servirá.
Un ejemplo de utilización de Textbox
aparece en su propio código fuente, ver
Listado 4 y Figura 2. Este código genera
un rectángulo con bordes (usando la fun-
ción rectangle de curses.textpad) y nos
solicita que escribamos algo en el mismo.
Para acabar debemos pulsar control+g,
mostrándonos lo escrito más abajo. Si lo
probamos comprobaremos que no pode-
mos salir del rectángulo al editar.
En la Figura 3 vemos como hemos inte-
grado el Textbox en nuestro programa.
Hemos aumentado el número de colum-
nas para permitir introducir mensajes
más largos.
El Gestor de ComandosExisten muchas maneras de hacer un ges-
tor de comandos. La más típica consiste
en hacer una sentencia switch o gran can-
LIBRERÍAS Curses
64 PYTHON W W W. L I N U X - M A G A Z I N E . E S
[1] Descargas de los listados de este artí-
culo: http://www.linux-magazine.es
/Magazine/Downloads/Especiales/06_
Python
Recursos
Tabla 1: Algunos métodos especiales de PythonMétodo Descripción
__len__(self) devuelve la longitud de nuestro objeto. Se invoca cuando se ejecuta
len(miObjeto)
__setitem(self, clave,valor) se corresponde con la asignación en un diccionario:
miObjeto[“Algo”] = “otra cosa”
__getitem(self, clave) es el equivalente miObjeto[“Algo”] y devuelve la información alma-
cenada en “Algo”.
__delitem(self, clave) es del miObjeto[“Algo”] y se corresponde con la eliminación de esa
entrada.
La representación gráfica en 3D ofrece
la posibilidad de crear mundos virtuales
en un ordenador, lo cual, unido a la
visualización permite al usuario explorar
y entender, rápidamente, sistemas com-
plicados. Esto es posible gracias al
avance de lenguajes orientados a obje-
tos, que ofrecen la posibilidad de crear
software de mejor calidad y más fácil de
mantener.
Entre las diferentes herramientas de
visua lización, representación 3D y
procesamiento de imágenes, cabe
destacar VTK (Visualization Toolkit),
código abierto cuyo núcleo está imple-
mentado en C++ y que soporta
envolturas (“wrappers”) para TCL,
Python y Java, permitiendo el desarrollo
de aplicaciones complejas de un modo
eficiente y mediante scripts sencillos. Por
todo ello, VTK se emplea en la visua -
lización médica, la visualización indus-
trial, reconstrucción de superficies a par-
tir de digitalización láser o nubes de
puntos desorganizados, etc.
En lo que sigue veremos los conceptos
básicos en los que se basa VTK para
poder generar una escena y, mediante
una serie de ejemplos desarrollados en
Python, llegaremos a crear nuestras
propias escenas de visualización.
InstalaciónPara poder realizar todas las pruebas que
se van sugiriendo y las que se os ocu -
rran, es necesario tener instalado Python
y VTK con soporte para Python. Además,
la tarjeta gráfica de nuestro ordenador
debe tener OpenGL funcionando.
La instalación de las librerías VTK
(que no suelen estar instaladas de man-
era predeterminada) es muy sencilla.
Todas las distros mayoritarias cuentan
con los paquetes necesarios en sus
repositorios.
En Debian o Ubuntu, por ejemplo,
bastará con ejecutar
apt-cache search vtk
para ver las que necesitamos.
Modelos de Objetos VTKPara los inexpertos en el mundo de la
visualización, vamos a explicar de un
modo sencillo la estructura de VTK, ya
que esto permite que comprendamos
mejor cada uno de los pasos que iremos
realizando. Por un momento, imaginad
que estáis en la butaca del cine, viendo
una película de animación, 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, cámaras que modifican el punto
de vista, propiedades de los personajes
(color, forma, etc.). Aunque no lo creáis,
todos estos conceptos son la base de la
visualización gráfica. Veamos dicha
estructura.
El toolkit de visualización VTK está
diseñado a partir de dos modelos clara-
mente diferenciables: el modelo gráfico y
el modelo de visualización.
• Modelo gráfico. El modelo gráfico
captura las principales ca racterísticas
de un sistema gráfico 3D, de un modo
fácil de entender y usar (ver Figura
1). La abstracción se basa en la indus-
tria del cine. Los objetos básicos que
constituyen este modelo son: vtkRen-
derer, vtkRenderWindow, vtkLight,
vtkCamera, vtkProp, vtkProper ty,
vtkMapper, vtkTransform. En la Tabla
1 se describen cada uno de estos obje-
tos.
• Modelo de visualización. El papel del
modelo gráfico es transformar datos
gráficos en imágenes, mientras que el
del modelo de visuali zación trans-
forma información en datos gráficos;
esto significa que el modelo de visual-
ización es el responsable de construir
la representación geométrica que se
renderiza mediante el modelo gráfico.
VTK se basa en la aproximación de los
datos para transformar la información
en datos gráficos. Hay dos tipos bási-
cos de objetos, descritos en la Tabla 2,
involucrados en dicha aproximación:
vtkDataObject y vtkProcessObject.
LIBRERÍAS3D con VTK
65PYTHONW W W. L I N U X - M A G A Z I N E . E S
Visualización 3D con VTK (Visualization Toolkit)
Gráficas 3DHoy por hoy, la representación gráfica 3D y su visualización forman
parte de nuestra vida cotidiana; basta fijarse en el mundo del
entretenimiento, en la industria del juego y en el soporte de hard-
ware y software para tales fines. ¿Quién en su ordenador personal
no ha instalado un juego o visto una película renderizada en 3D?
Por Ana M. Ferreiro Ferreiro y José A. García Rodríguez
Figura 1: Estructura del modelo gráfico.
Figura 2: Tipos de datos: a) datos poligo-
nales, b) puntos estructurados c) malla no
estructurada d) malla estructurada.
LIBRERÍAS 3D con VTK
66 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Los diferentes tipos de datos que pueden
constituir un objetos son, entre otros,
puntos, rectas, polígonos, puntos estruc-
turados, mallas estructuradas y no
estructuradas, etc. (ver Figura 2).
Mi Primera EscenaYa estamos preparados para construir
nuestra primera escena. Situaros en el
papel de director de cine. En los si -
guientes ejemplos veremos el modo de
emplear las clases que acabamos de
describir. Para ello, tal como se men-
ciona al comienzo, instanciaremos VTK
desde Python.
Con cualquier editor de textos,
creamos el fichero cone.py. Lo primero
es importar desde Python el paquete
VTK; esto es tan sencillo como escribir la
siguiente línea:
import vtk
Ahora que ya podemos instanciar
cualquier objeto de VTK, sin más que
escribir vtk.nombre_clase, necesitamos
crear nuestra ventana de renderizado
vtk.vtkRenderWindow, a la que llamare-
mos renWin y a la que asociamos un
área de renderizado vtk.vtkRenderer (que
denominamos ren), mediante el método
AddRenderer(). Escribamos las siguie n -
tes líneas de código:
ren=vtk.vtkRenderer()
renWin=vtk.vtkRenderWindow()
renWin.AddRenderer(ren)
iren=vtk.U
vtkRenderWindowInteractor()
iren.SetRenderWindow(renWin)
Para poder manipular la cámara median -
te el ratón se ha instanciado el objeto
vtkRenderWindowInteractor (denomi-
nado en el código como iren). Nótese
que la ventana de renderizado renWin se
asocia al objeto de interacción iren medi-
ante el método SetRenderWindow. En
este momento no se aprecia la utilidad
del mismo, paciencia… ya compren-
deréis su importancia cuando tengamos
un actor en nuestra escena.
Guardamos el fichero y en la línea de
comandos ejecutamos el programa tecle-
ando python cone.py… ¡No ocurre nada!
Esto es porque debemos inicializar la
interacción del usuario e indicar que la
ventana de renderizado permanezca visi-
ble hasta que el usuario finalice la ejecu-
ción de la misma cerrándola. Para ello
basta escribir
iren.Initialize()
iren.Start()
Si ejecutamos nuevamente el programa,
se abre una ventana de color negro con
sus botones de minimizar, maximizar y
cerrar; y que sólo se cierra cuando el
usuario lo estima oportuno (Figura 3).
Esta ventana va a ser “el contenedor” de
nuestra pequeña escena. Nótese que las
dos líneas de código que acabamos de
escribir deben de estar al final del
fichero. Las demás líneas que escribamos
a partir de este momento debemos situar-
las justo antes.
Para crear nuestro primer actor no nos
vamos a complicar demasiado, porque
ya queremos ver algo. VTK contiene una
serie de clases que nos permiten crear
objetos tridimensionales sencillos, como
son: esfera (vtkSphereSource), cono (vtk-
ConeSource), cilindro (vtkCilinder-
Source), etc. Para nuestro ejemplo hemos
escogido un cono, sin embargo, puedes
optar por cualquiera de los otros objetos.
El siguiente código nos permite crear
nuestro primer “actor”,
cone=vtk.vtkConeSource()
coneMapper=vtk.U
vktPolyDataMapper()
coneMapper.SetInput(cone.U
GetOutput())
coneActor=vtk.vtkActor()
coneActor.SetMapper(coneMapper)
Mediante el objeto vtk.vtkConeSource
creamos una representación poligonal
01 import vtk
02
03 # Generamos la estructura para
ver un cono
04 cone = vtk.vtkConeSource()
05 coneMapper = vtk.vtk
PolyDataMapper()
06 coneMapper.SetInput(cone.
GetOutput())
07 coneActor = vtk.vtkActor()
08 coneActor.SetMapper(coneMapper)
09
10 # Crear fuente de esfera,
mapeador y actor
11 esfera = vtk.vtkSphereSource()
12 esferaMapper = vtk.
vtkPolyDataMapper()
13 esfera.SetPhiResolution(10)
14 esfera.SetThetaResolution(20)
15 esfera.SetCenter(0.3,0.0,0.0)
16 esferaMapper.SetInput(esfera.
GetOutput())
17 esferaActor = vtk.vtkActor()
18 esferaActor.SetMapper
(esferaMapper)
19 esferaActor.GetProperty().Set-
Color(0.7,0.0,0.25)
20 esferaActor.GetProperty().
SetOpacity(0.75)
21 esferaActor.GetProperty().
SetLineWidth(1)
22
23 # Creamos: Renderer, Render
Window, RenderWindowInteractor
24 ren = vtk.vtkRenderer()
25 renWin = vtk.vtkRenderWindow()
26 renWin.AddRenderer(ren)
27 iren = vtk.vtkRenderWindow
Interactor()
28 iren.SetRenderWindow(renWin)
29
30 # Añadimos el actor en el área
de renderizado (Renderer)
31 ren.AddActor(coneActor)
32 ren.AddActor(esferaActor)
33
34 #Fijamos el color de fondo, el
tamaño y hacemos zoom sobre
35 #el area de Renderizado
36 ren.SetBackground(1, 1, 1)
37 renWin.SetSize(450, 425)
38 camera=ren.GetActiveCamera()
39 ##camera.Zoom(1.5)
40
41 coneActor.RotateX(30)
42 coneActor.RotateY(45)
43 conepro=coneActor.GetProperty()
44 conepro.SetColor(0,0.6,1)
45 ##conepro.SetOpacity(0.5)
46 conepro.SetLineWidth(2)
47 ren.ResetCamera()
48 ##camera=ren.GetActiveCamera()
49 camera.Zoom(1.5)
50
51 cone.SetResolution(40)
52
53 iren.Initialize()
54 renWin.Render()
55 iren.Start():
Listado 1: cono_esfera.py
LIBRERÍAS3D con VTK
67PYTHONW W W. L I N U X - M A G A Z I N E . E S
de un cono, que hemos llamado cone.
La salida del cono (cone.GetOutput())
es un conjunto que se asocia al “map-
per” (coneMapper) (vtk.vtkPoly-
DataMapper) vía el método SetInput().
Creamos el actor (objeto que se va ren-
derizar) al que se le asocia la repre-
sentación geométrica que aporta
coneMapper. Nótese que los pasos aquí
indicados son los que, en general, nece-
sitamos seguir para poder cons truir un
actor (Figura 4).
Cuando creamos un actor, no se
incluye por defecto en la escena. Es
necesario añadirlo al Renderer median te
AddActor, y posteriormente renderizar la
escena. Esto se logra escri biendo,
ren.AddActor(conoActor)
renWin.Render()
Si volvemos a ejecutar, visualizamos un
cono de color gris (color que se muestra
por defecto) dentro de nuestra ventana
(Figura 5). Además, es en este instante
cuando se aprecia la interacción con el
ratón; con el botón izquierdo puedes
rotar la cámara, el botón central permite
trasladarla, y con el botón derecho nos
acercamos o alejamos del objeto.
Además, habrás observado que en la
escena, por defecto se incluye una luz
para poder visualizar los objetos ilumi-
nados.
Prueba a comentar la línea
renWin.Render(). ¿Qué ocurre? Como
habrás podido observar el cono ya no
aparece, esto es porque cada vez que
añadimos un actor es necesario ren-
derizar la escena, ya que de lo contrario
no se rea liza un refresco de la misma y
es como si no hubiésemos añadido un
nuevo actor.
Propiedades de ObjetosSi has seguido el tutorial hasta este
punto, habrás creado tu cono de color
gris. Pero probablemente no estés
demasiado satisfecho, porque todos te -
nemos el mismo cono gris y tú
lo querías blanco y el fondo
azul, por ejemplo. A lo largo
de este apartado veremos
cómo modificar la ventana de
renderizado, la cámara,
propiedades del actor, etc. Al
final, podrás realizar todos
aquellos cambios que te
apetezcan.
Habrás observado que la ventana de
renderizado se abre con un tamaño pre-
determinado. Para fijar el tamaño de
dicha ventana es necesario emplear el
método SetSize, donde indicamos el alto
y el ancho en pixels,
renWin.SetSize(450,325)
Figura 3: Pasos que en general hay que
seguir para crear un actor.
Tabla2: Modelo de VisualizaciónObjeto Descripción
vtkDataObject clase genérica que permite representar diferentes tipos de datos. Los obje-
tos de datos consisten en estructuras geométricas y topológicas (puntos y
celdas), y también en atributos asociados, tales como escalares o vectores.
vtkProcessObject objeto que hace referencia a filtros, que actúan sobre los actores modificán-
dolos.
Tabla 1: Modelo GráficoObjeto Descripción
vtkRenderer crea un área de renderizado que coordina: luces, cámaras y actores.
vtkRenderWindow clase que representa el objeto dentro del cual se colocan una o más á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 geometría 3D es proyectada dentro de la
imagen 2D durante el proceso de renderizado. La cámara tiene diferentes
métodos que permiten definir el punto de vista, el foco y la orientación.
vtkProp objeto que representa los diferentes elementos (actores) que se sitúan den-
tro de la escena. Caben destacar las siguientes subclases: vtkActor, vtkVol-
ume, vtkActor2D.
vtkProperty representa los atributos de renderizado de un actor, incluyento color, ilumi-
nación, mapeado de la estructura, estilo de dibujo y estilo de la sombra.
vtkMapper representa la definición de la geometría de un actor y mapea los objetos
mediante una tabla de colores (vtkLookupTable). El “mapper” proporciona
la frontera entre el modelo de visualizión y el modelo gráfico.
vtkTransform objeto consistente en una matriz de transformación 4x4 y métodos para
modificar dicha matriz. Especifica la posición y orientación de actores,
cámaras y luces
Figura 5: Cono dentro de
la escena.
Figura 4: Ventana de ren-
derizado por defecto.
Figura 6: Comportamiento de los métodos de
la cámara. a) Azimuth - flechas rojas; b)
Pitch - flechas azul celeste; c) Yaw - flechas
azul oscuro; d) Elevation - flechas verdes; e)
Roll - flecha amarilla. La esfera blanca repre-
senta el foco.
LIBRERÍAS 3D con VTK
68 PYTHON W W W. L I N U X - M A G A Z I N E . E S
Si lo que pretendemos es cambiar el
color de fondo de la escena (vtkRen-
derer) empleamos el método SetBack-
ground (RGB), donde le pasamos el color
deseado en formato RGB. Si queremos
un fondo azul bastaría escribir
ren.SetBackground(0.0, 0.0, 1)
Como dijimos, el área de renderizado
(vtkRenderer) coordina la cámara y las
luces. Mediante el método GetActive-
Camera() se accede a la cámara creada
en la escena, así podemos aplicarle todos
los métodos del objeto vtkCamera para
poder modificar la visualización según
queramos. Si lo que pretendemos es que
todos nuestros actores se vean en su
totalidad dentro del área de renderizado,
es necesario llamar al método ResetCa -
mera(). En las siguientes líneas se reco-
gen algunos de los métodos relativos a la
cámara
ren.ResetCamera()
camera=ren.GetActiveCamera()
camera.Azimuth(60)
camera.Pitch(5)
camera.Yaw(5)
camera.Roll(50)
camera.Elevation(20)
camera.Zoom(1.5)
Los métodos Azimuth, Pitch, Yaw,
Roll,Elevation se ocupan de rotar la
cámara o el punto de foco en diferentes
direcciones y, como argumento, se pasa
un ángulo de rotación. Lo mejor es que
juegues un poco con la cámara y veas lo
que ocurre probando cada uno de estos
métodos por separado. Por ejemplo, para
ver cómo afecta el método Azimuth apli-
cado a la cámara, comenta las restantes
lineas de código, por que si no estarías
mezclando distin-
tos métodos de
rotación y uno así
no sabe realmente
lo que ocurre. Si
en algún
momento el actor
desaparece de la
escena no te pre-
ocupes, lo que
está sucediendo
es que el ángulo
de rotación ha
colocado la cámara justo en un punto
que evita que visualicemos el objeto
dentro de la escena. En la Figura 6 se
explica de un modo sencillo el modo en
que actúan cada uno de estos métodos
respecto del foco (representado por una
esfera blanca).
A partir de este punto comentad las
líneas de código correspondientes a los
métodos que actúan sobre la cámara,
dejando únicamente la línea came -
ra.Zoom(1.5). Así vamos viendo cada
cosa por separado, después ya tendréis
tiempo de mezclar código.
Ahora que sabemos modificar la
escena, debemos recordar que el cono
continúa viéndose en un color gris un
poco apagado. Para acceder a las
propiedades de cualquier actor vtkActor
se emplea el método GetProperty(), que
devuelve una instancia del objeto
vtkProp asociado a dicho actor. Las
siguientes líneas permiten modificar el
color, la transparencia y grosor de las
líneas:
conepro=coneActor.GetProperty()
conepro.SetColor(1,0.2,0)
conepro.SetOpacity(0.5)
conepro.SetLineWidth(3)
conepro.SetResolution(40)
conepro.U
SetRepresentationToWireframe()
La última línea de este código se indica
que queremos ver la estructura básica
que constituye el actor, es decir, el ma -
llado. Por defecto, VTK tiene asocia das
teclas rápidas a la escena: si tecleas la
letra “s” se ven todos los objetos rende -
rizados (ver Figura 7), mientras que si
tecleas la letra “w”, se visualiza sólo la
malla (ver Figura 8). Ahora apreciarás
mejor la diferencia entre mallado y
estructura renderizada, no hay nada
mejor que poder ver las cosas.
La línea conepro.SetResolution(40)
modifica la resolución con la que se ren-
deriza el cono. Este método no es ge neral
para todos los actores, sino para ciertos
objetos que VTK ya incluye, como son:
esfera (vtkSphereSource), cono (vtkCone-
Source), cilindro (vtkCilinderSource), etc.
Cualquier objeto puede rotarse,
escalarse, obtener su dimensiones, etc.,
utilizando las propiedades de un vtkActor
en particular(si queréis más información,
basta consultar las ayuda de VTK sobre
vtkProp3D, que es la clase padre). Para
rotar nuestro cono y escalarlo basta
escribir
coneActor.RotateX(30)
coneActor.RotateY(45)
coneActor.SetScale([1,3,2])
Ahora ya sabéis crear vuestra propia
escena, modificar sus propiedades,
añadir un actor con las opciones que
queráis y modificar la cámara. En caso
de que quisierais añadir más actores a
vuestra ventana de renderizado, basta
seguir el mismo procedimiento que
hemos empleado para crear nuestro
cono. En el Listado 1 se añade a la
escena un cono y una esfera que se inter-
secan (Figura 9). ■
[1] RPMs VTK de Mandrake:
ftp:// ftp. rediris. es/ sites3/ carroll. cac.
psu. edu/ mandrakelinux/ official/ 10. 1/
i586/ media/ contrib/
[2] Kitware. VTK:
http:// www. kitware. org
[3] Enthought. Scientific python:
http:// www. scipy. org
[4] MayaVi: http:// mayavi. sourceforge. net
[5] Código de este artículo: http://www.
linux-magazine.es/Magazine/Down-
loads/Especiales/06_Python
Recursos
Figure 7: Vista de la superficie
del cono.
Figura 9: Escena con dos actores
que se intersecan.
Figura 8: Vista de la malla del cono.
Cada vez que vemos el lanzamiento de
un cohete, todos quedamos asombrados
ante la explosión del despegue, la atenta
mirada de todos esos científicos a los
paneles de control, y la monstruosa cifra
que nos dicen que se han gastado en el
proyecto.
¿ Donde Van los Impuestos?Es entonces cuando surge la pregunta ¿y
eso a mí en qué me repercute? Un día,
estando en el despacho de la Rama del
IEEE de Málaga tuve una conversación
en la que me contaron que la mayor
parte de los satélites emiten “al mundo”
las imágenes y los datos que recogen. Es
decir, si se posee el equipo necesario es
posible recibir en tu propia casa imáge-
nes fascinantes del universo, de Marte o
de la Tierra.
La temperatura del océano, imágenes
meteorológicas, imágenes del campo
magnético del sol o de las misiones a
Marte son enviadas constantemente a la
Tierra desde estos engendros espaciales.
Y el efecto es siempre el mismo, el espec-
tador es deslumbrado por el presentador
de televisión con unas imágenes increí-
bles mientras se escuchan acordes de
sintetizador.
¿Acaso no son esas imágenes de domi-
nio público? ¿Dónde puedo conseguirlas?
En el presente artículo utilizaremos
Python para crear un script CGI que nos
permita recoger y mantener actualizadas
las imágenes que queremos en una espe-
cie de “collage” o mural. Construiremos
nuestro propio centro de control espa-
cial.
Recoger las ImágenesLo primero será encontrar las imágenes y
reunirlas. Vamos a usar como ejemplo
cuatro imágenes de carácter científico.
Se actualizan a distintos intervalos, de
manera que podremos ver cómo evolu-
cionan las eventos que se registran.
Puedes encontrar las URLs en Recursos
[1].
Debemos descargar las imágenes y
almacenarlas dentro de nuestro progra-
LIBRERÍASPIL
69PYTHONW W W. L I N U X - M A G A Z I N E . E S
¿Quién no ha querido alguna vez sentirse como esos informáticos de la NASA en su centro de control? Hoy
nos construiremos el nuestro y controlaremos el planeta y sus alrededores.
Por José María Ruíz y Pedro Orantes
Imágenes satélite en Python
Vigilantes del Planeta
LIBRERÍAS PIL
70 PYTHON W W W. L I N U X - M A G A Z I N E . E S
ma; haremos uso de la librería httplib,
que es parte de la distribución estándar
de Python. Esta librería nos permitirá
hablar de tú a tú con un servidor web
remoto sin tener que preocuparnos de
los detalles de más bajo nivel. Esta con-
versación la realizaremos usando el pro-
tocolo HTTP. Este protocolo es bastante
simple, y de él solo necesitaremos una
parte mínima.
Cuando Tim Berners Lee realizó el
diseño original de la Web, quiso que el
protocolo para pedir los documentos
fuese lo más simple posible. HTTP se
reduce a la recepción y el envío de infor-
mación al servidor, eso y sólo eso. Se
compone de varios comandos, pero los
más conocidos son GET, que podemos
traducir como “tomar”, y POST, que
podemos traducir en este contexto como
“enviar” o “mandar”. Así que tomamos
documentos y enviamos información.
Una parte importante de HTTP es URL,
que nos sirve para darle nombre a esos
documentos. Todos estamos acostumbra-
dos a tratar con urls, generalmente del
tipo http:// www. linux-magazine. es/
issue/ 08. La url se compone de: [protoco-
lo]:// [maquina]/ [ruta]/ [objeto]. Vamos la
ver ahora porqué es tan importante que
sepamos esto.
La librería httplib de Python establece
en un primer paso una conexión con el
servidor remoto mediante el método
HTTPConnection
>>> c = httplib.HTTPConnectionU
("www.linux-magazine.es")
>>>
En la variable c almacenamos el objeto
que representa la conexión realizada y
podemos enviar peticiones.
>>> c.request("GET","/issue/08")
>>>
Usamos el comando GET, con lo que
estamos solicitando un objeto. El segun-
do parámetro del método es la “ruta”
hasta el objeto. Así que la URL que esta-
mos solicitando es http:// www.
linux-magazine. es/ index. html. Es impor-
tante que la ruta comience con una barra
“/ ”, como si fuese la ruta de un fichero
de una máquina. ¿Cómo sabemos si todo
ha ido bien?
>>> r = c.getresponse()
>>> print r.status,r.reason
200 OK
>>>
Con getresponse podemos conseguir un
objeto que representa los datos devuel-
tos por la conexión. Este objeto tiene,
entre otros, los atributos status y reason
que nos indican el estado, un número
con un significado especial, y la explica-
ción del mismo. En este caso todo ha ido
bien, y por eso recibimos un “OK”. En
caso contrario, si no existiese la ruta que
pedimos habríamos obtenido:
>>> r = c.getresponse()
>>> print r.status, r.reason
400 Bad Request
>>>
Ahora ya tenemos la página, sólo tene-
mos que leerla-- usando el método
read() del objeto respuesta.
>>> print r.read()
<html>
<head>
<base href="http://www.U
linux-magazine.es/issue/08/" />
<title>Linux Magazine -U
Spamassasin, Hypermail,U
Encriptación GPG, SDL,U
...
Cuando hayamos finalizado debemos
cerrar la conexión invocando el método
close() del objeto que representa la cone-
xión, en este caso sería:
>>> c.close()
>>>
Para obtener las imágenes vamos a hacer
exactamente lo mismo, abriremos una
conexión, pediremos la imagen, la alma-
cenaremos en un diccionario y cerrare-
mos la conexión.
Python Imaging LibraryNuestra idea original era realizar un
mural o collage con las imágenes recupe-
radas. Python no nos provee de una
librería de tratamiento gráfico en su dis-
tribución estándar. Eso no quiere decir
que no exista tal librería, ya que no sólo
existe, sino que además es muy potente
y útil. Nos referimos a Python Imaging
Library (ver URL [2] en el Listado de
Recursos al final del artículo).
La librería PIL (Python Imaging
Library) nos va a permitir tratar imáge-
Figura 1: La imagen original que vamos a
modificar con PIL.
Figura 2: La imagen del pequeño demonio de
BSD rotado 45º con PIL.
Poco tiempo después de finalizar este
artículo apareció una noticia en Slashdot
(ver Recursos [4]) hablando de una lla-
marada solar de tal tamaño que iba a
alterar las comunicaciones. Cuando se
dan este tipo de eventos en muchos cen-
tros de control de satélites, los ingenie-
ros cruzan los dedos para que sus satéli-
tes no caigan ante la ola de viento solar
que se origina. El lector puede apreciar
la llamarada en la Figura 4.
Curiosidad
01 >>>mural =
Image.new('RGB',(600,480))
02 >>> im =
Image.open("daemon.jpg")
03 >>> im.thumbnail((300,200),
Image.ANTIALIAS)
04 >>> mural.paste(im,(0,0))
05 >>> mural.paste(im,(300,0))
06 >>> mural.show()
07 >>>
Listado 1: Ejemplo Uso de PIL
LIBRERÍASPIL
71PYTHONW W W. L I N U X - M A G A Z I N E . E S
nes en una gran cantidad de formatos.
Podremos convertirlas a otro formato,
rotarlas, escalarlas, mezclarlas, etc.
Aquel lector que haya tenido contacto
con programas de manipulación gráfica,
como por ejemplo GIMP (ver URL [3] en
el Listado Recursos), comprenderá la
potencia de una librería con estas funcio-
nalidades.
Como no viene de serie con Python,
deberemos instalarla en nuestra distribu-
ción o sistema operativo. Existen paque-
tes RPM y DEB de la misma.
¿Cómo se trabaja con PIL? Pues
mediante la manipulación de objetos de
la clase Image. Esta clase es capaz de
albergar imágenes de casi cualquier for-
mato, permitiéndonos manipularlas.
Vemos un ejemplo. En la Figura 1 pode-
mos ver la imagen original del fichero
daemon.jpg en mi equipo. Vamos a rotar-
la 45 grados:
>>> import Image
>>> im = Image.openU
("daemon.jpg")
>>> img.rotate(45).show()
>>>
En la Figura 2 podemos ver el resultado.
Hemos usado el método rotate(), al que
hemos pasado un ángulo de 45 grados, y
en el resultado hemos invocado el méto-
do show() que mostrará el resultado
mediante el programa xv (para cerrar xv
sólo tenemos que pulsar “q”).
Nosotros no buscamos rotar imágenes,
sino escalarlas. Las imágenes presentes
en la web suelen ser de gran tamaño, y
nosotros queremos crear un mural de un
tamaño estático. Tendremos que adaptar
las imágenes descargadas para que que-
pan en el mural.
Para hacerlo vamos a insertar las imá-
genes en una mayor, pero hay muchas
maneras de hacer esto. La solución que
adaptaremos en nuestro caso es la de
dividir la imagen-mural en tantos recua-
dros como imágenes vayamos a insertar.
¿Cómo sabremos la cantidad de cuadrí-
culas? Pues escogeremos la menor
potencia de 2 que sea mayor que nuestro
número de imágenes. No es muy compli-
cado; por ejemplo, si tenemos 7 imáge-
nes, 8 (2 elevado a 3) será suficiente.
Básicamente multiplicaremos 2 por sí
mismo hasta que sea mayor que el
número de imágenes que queramos mos-
trar. Gráficamente lo que haremos será ir
dividiendo en anchura y en altura la ima-
gen en cuadrículas, en cada iteración se
multiplicará por 2 el número de cuadrí-
culas. Con este método perderemos
espacio en la imagen, pero al ser tan sen-
cillo no complicará mucho el código.
Creemos el thumbnailPrimero creemos una imagen vacía, ver
Listado 1. La Figura 3 muestra el resulta-
do. En la imagen vacía que creamos esta
vez no cargamos ninguna imagen, sino
que usamos el método new() que necesi-
ta el tipo de pixel (‘RGB’ viene de Red-
>Rojo Green->Verde Blue->Azul, es
uno de los formatos estándar) y el tama-
ño de la imagen medido en pixels. En
nuestro caso hemos escogido 600 pixels
de ancho por 480 de alto (presta atención
a los “()”, porque la resolución se expre-
sa como una secuencia del tipo “(x,y)” ).
Esta nueva imagen no contiene nada, a
excepción de un decepcionante fondo
negro. ¡Vamos a poner algo de color!
Cogemos la imagen del “daemon” e
invocamos el método thumbnail(), que
escala la imagen tanto vertical como
horizontalmente. Tenemos que pasarle el
tamaño deseado como una secuencia; la
nueva imagen tendrá un tamaño de
300x200 pixels. Puede aceptar un pará-
metro adicional, en nuestro caso es
Image.ANTIALIAS, que debería mejorar
la resolución de la nueva imagen.
A continuación usamos el método
paste() de Image, que nos permite
“pegar” una imagen dentro de otra en las
coordenadas indicadas como segundo
parámetro. Pegamos la imagen “dae-
mon” dos veces, la primera en la posi-
ción (0,0) del mural y la segunda en la
posición (300,0). Podemos ver el resulta-
do usando el método show().
El Fichero de ConfiguraciónLas URLs y la resolución deben ser reco-
gidas por el programa, pero ¿cómo?
Existen varias opciones-- podríamos
pasárselas al programa cuando se ejecu-
te. Las URLs tienen el problema de ser
bastante largas en ocasiones, así que la
linea de comandos para ejecutar el pro-
grama puede ser engorrosa.
En lugar de eso vamos a usar un fiche-
ro de configuración. Cada vez que el pro-
grama se ejecute, leerá este fichero y
recogerá los parámetros oportunos.
¿Qué forma tendrá el fichero? La últi-
ma tendencia es crear ficheros XML de
configuración. Pero el XML puede ser
demasiado complicado si tenemos en
cuenta que nuestro fichero de
configuración puede no tener más de 10
líneas. En UNIX, la tendencia es la de
usar el formato de “clave = valor”, y ese
es el que usaremos. El fichero será como
el que se muestra en el Listado 2.
Leeremos cada línea del fichero, la
dividiremos usando el “=” y usaremos
la primera parte como clave en un dic-
Figura 3: Creamos una imagen vacía con PIL
y después colocamos otras imágenes en su
interior como mosaico.
Figura 4: Llamaradas solares que amenazan
con dejar fuera de combate a los satélites de
comunicadciones.
01 [tamaño]
02 horizontal = 800
03 vertical = 600
04
05 [imágenes]
06 url1 = http://www-mgcm.arc.
nasa.gov/MarsToday/marstoday.
gif
07 url2 = http://www.sec.noaa.
gov/sxi/current_sxi_4MKcorona.
png
08 url3 = http://www.ssec.wisc.
edu/data/sst/latest_sst.gif
09 url4 = http://www.wetterzen-
trale.de/pics/D2u.jpg
Listado 2: collage.conf
LIBRERÍAS PIL
72 PYTHON W W W. L I N U X - M A G A Z I N E . E S
cionario, y la segunda como valor. Si ya
existe la clave, usaremos una lista como
valor con los distintos valores como
entrada. Pero ¿por qué vamos a realizar
nosotros el trabajo duro cuando alguien
ya lo ha resuelto?
Python trae en su distribución están-
dar una librería que nos será de enorme
utilidad. Alguien consideró oportuno ela-
borar un analizador de archivos de
configuración, se llama ConfigParser.
Con ella podemos extraer la información
del archivo de configuración.
El archivo de configuración se compo-
ne de “Secciones” y “Opciones”. Cada
sección contiene varias opciones, y los
nombres de las secciones y opciones
deben ser únicos. Por eso las URLs
comienzan con “url1”, “url2” y “url3”.
Pero esto no será un problema, vemos
cómo funciona ConfigParser (ver Listado
3). Como podemos apreciar en el ejem-
plo, el uso de ConfigParser es muy senci-
llo. Primero se crea el analizador, guar-
dándolo en la variable config. Después
cargamos con el método readfp() el
fichero de configuración; este método
también analiza el fichero. A partir de
ese momento podemos realizar pregun-
tas al objeto almacenado en config. Con
sections() obtenemos una lista de las
secciones y con options() de las opcio-
nes. Con esa información ya podemos
recoger los datos necesarios usando el
método get(), al que pasamos una sec-
ción y una opción.
Ensamblemos las PartesAhora ya tenemos:
• Un sistema de configuración, usando
ConfigParser.
• Un sistema para descargar las imáge-
nes, usando httplib.
• Un sistema para manipular las imáge-
nes, usando PIL.
Nos toca ahora unirlo todo para que
genere la página que aparece en la
Figura 5. El resultado final se puede des-
cargar de [5].
Crearemos una clase Collage con los
métodos
• __cargaConf()
• __descarga()
• __totalXY()
• generaCollage()
• __generaImagen()
• __generaHTML()
Cuando un método comienza con “__”
se convierte en privado. Cualquier inten-
to de hacer uso de
ese método generará
una Excepción. Por
tanto, esos métodos
no pueden ser invo-
cados desde fuera
del objeto Collage.
De esta manera
Collage sólo tiene un
método accesible
desde el exterior,
genera Collage(). Se
ha separado la gene-
ración de HTML de
la del collage para
posibilitar las futu-
ras extensiones del
objeto. Por ejemplo,
podríamos no que-
rer generar un fichero HTML sino incor-
porar la imagen en un programa. En tal
caso heredaríamos de Collage y crearía-
mos un nuevo método generaCollage()
que sólo generase la imagen y la devol-
viese.
El método __generaHTML() genera el
código HTML de la página web. Un
punto a resaltar es que genera un mapa
sobre el collage, de manera que sea
posible pulsar sobre las distintas imáge-
nes que en él aparecen. Al hacerlo se
cargará la imagen a tamaño natural. El
mapa se genera recorriendo el dicciona-
rio de imágenes. Cada entrada del dic-
cionario contiene un objeto de la clase
Imagen.
Imagen alberga la información de cada
imagen descargada mientras el programa
la almacena. Se almacenan los datos pro-
pios de cada imagen, como por ejemplo
las coordenadas que ocupará finalmente
en el collage.
Como siempre, se espera que el lector
dedique algo de tiempo a jugar con el
programa para adaptarlo a sus necesida-
des o ideas.
ConclusiónLa complejidad de un programa Python
no depende de la cantidad de líneas de
código que contenga, sino más bien del
nivel al que trabaje. En el programa de
este artículo hemos hecho uso intensivo
de librerías que han realizado acciones
muy complicadas por nosotros. Python
posee una amplio abanico de librerías a
explotar, muchas de ellas con años de
desarrollo esperando a programadores con
ideas originales que poner en práctica. ■
Figura 5: Nuestro panel de control espacial terminado y colocado en
una página web generada dinámicamente.
01 >>> config =
ConfigParser.ConfigParser()
02 >>> config.readfp(open('colla-
ge.conf'))
03 >>> config.sections()
04 ['tamaño', 'imágenes']
05 >>>
06 >>> config.options('imagen')
07 ['url1', 'url3', 'url2']
08 >>>
09 >>>
config.get('imagen','url1')
10 '"http://www-mgcm.arc.nasa.
gov/MarsToday/marstoday.gif"'
Listado 3: Uso de ConfigParser
[1] Gráficos que usaremos:
http:// www-mgcm. arc. nasa. gov/ Mars
Today/ marstoday. gif
http:// www. sec. noaa. gov/ sxi/
current_sxi_ 4MKcorona. png
http:// www. ssec. wisc. edu/ data/ sst/
latest_sst. gif
http:// www. wetterzentrale. de/ pics/
D2u. jpg
[2] Python Imaging Library:
http:// www. pythonware. com/
products/ pil/
[3] The Gimp: http:// www. gimp. org
[4] Noticia sobre llamarada solar en
Slashdot:
http:// science. slashdot. org/ science/ 05/
09/ 08/ 1933205. shtml?tid=215&tid=14
[5] Listado del programa final de este
artículo: http://www.linux-
magazine.es/Magazine/Downloads/Es
peciales/06_Python
Recursos
Digamos que llegas un día por la
mañana a la oficina. El jefe se acerca y te
pide que vuelvas a pasar, ¡otra vez!, un
montón de información a otra empresa a
través de la peor interfaz jamás dise-
ñada: una web.
Carga la página, introduce tus datos de
acceso, pincha aquí, pincha allá. Cuando
estás en la página con el formulario en
cuestión debes introducir los datos y pin-
char en un enlace o botón para enviar-
los. Una y otra vez, una y otra vez. Quizá
durante horas.
Acaso ¿no hay una mejor manera de
hacer esto? Lo ideal sería poder usar tu
hoja de cálculo preferida, rellenar los
campos en ella de forma rápida (aque-
llos que se repitan pueden ser copiados y
pegados) y cuando estuviese lista, hacer
algo (¿magia vudú?) y que se cargasen
solos en la dichosa web. Y por supuesto,
sin que se enterase el jefe, así tendrías
más tiempo para leer artículos como éste
;).
Pues sí, ¡existe una manera de hacer
exactamente lo que acabas de leer! Y
vamos a explicarlo en este capítulo. Así
podrás decirle a tu jefe que esta revista
hará a la empresa mucho más produc-
tiva.
MechanizeNo es la primera vez que hago esto. Hace
algunos años tuve este mismo problema
en la oficina en la que trabajaba. Había
que rellenar un formulario web para dar
parte de las ventas. Esa tarea, debido a
su volumen, requería que una persona
perdiese toda una mañana simplemente
porque a nadie en la otra empresa se le
ocurrió la idea de hacer el proceso más
rápido.
Así que ni corto ni perezoso mi jefe
creó un script en Perl que hacía este tra-
bajo a partir de un fichero de texto CSV.
El problema es que lo hizo en Perl, y esta
sección va sobre Python. ¿Hemos de
mudarnos todos a Perl para poder disfru-
tar de este tipo de ventajas? No, gracias a
que John J. Lee decidió portar la librería
Mechanize que Andy Lester creó en Perl
LIBRERÍASMechanize
73PYTHONW W W. L I N U X - M A G A Z I N E . E S
Python y la Web
EnredadosPodemos automatizar comandos y programas gráficos, ¿por qué no
automatizar la interacción con páginas web? En este artículo crearemos
un pequeño script que puede ahorrarnos mucho trabajo con el ratón.
Por José María Ruíz
LIBRERÍAS Mechanize
74 PYTHON W W W. L I N U X - M A G A Z I N E . E S
(ver Recurso [1]) a Python (ver Recurso
[2]).
Las distribuciones de Linux suelen per-
mitir instalarla como paquete. El pro-
blema es que la versión más completa no
está aún liberada, y es recomendable
emplear la que se encuentra en el servidor
de Subversion de John. El proceso es sim-
ple, instalamos el cliente de Subversion
que más nos guste, aquí usaré el estándar,
y descargamos el código fuente con:
> svn co http://U
codespeak.net/svn/wwwsearch/U
mechanize/trunk mechanize
John emplea la librería setuptools de
Python para compilar e instalar la libre-
ría, así que deberíamos proceder a insta-
larla antes de continuar. Después sólo
debemos ejecutar:
> python setup.py build
> sudo python setup.py install
Y listo. Ya tenemos nuestra librería
mechanize lista para trabajar.
Juguemos con una WebComencemos con algo simple. Vamos a
conectar con la web de Linux Magazine
y a pedirle que busque la palabra
“python” para, a continuación, conse-
guir una lista de las urls de los primeros
artículos que contengan esa palabra
(serán enlaces a ficheros PDF), ver
Figura 1. De esta forma comprobaremos
cómo se trabaja con mechanize, ya que
tiene una forma peculiar de tratar el
código HTML.
Para ello debemos comenzar por
arrancar python e importar la librería
mechanize y re (expresiones regulares):
>>> import re
>>> import mechanize
No ocurre nada particularmente vistoso,
a no ser que no hallamos instalado
correctamente la librería. Muy bien,
seguidamente necesitamos crear un
navegador:
>>> br = mechanize.Browser()
Ahora ya lo tenemos en la variable br.
No me refiero a un navegador gráfico,
sino a todo lo que un navegador puede
hacer pero sin la parte gráfica. Me
explico, un navegador posee un motor
que interactúa con los servidores web y
una interfaz gráfica que interactúa con el
usuario. La librería mechanize nos da lo
primero sin lo segundo. Nuestra “inter-
faz de usuario” serán llamadas a méto-
dos del objeto Browser.
Un problema que nos podemos encon-
trar, ya desde el principio, es que nuestra
red disponga de un proxy para acceder a
Internet. Este caso es bastante común,
así que debemos indicárselo al objeto
almacenado en br:
>>> br.set_proxies(U
{“http” : “192.168.1.254:8000”,U
“ftp” : “192.168.1.254:8000”})
Aquí he especificado un proxy para http
y otro para ftp, que suele ser lo normal.
Ya tenemos nuestro navegador listo. Sólo
tenemos que abrir una página:
>>>> respuesta= br.openU
(‘http://www.linuxmagazine.es/’)
Una vez abierta se nos devuelve un
objeto de respuesta. Este objeto contiene
todos los métodos necesarios para poder
trabajar con la información devuelta por
el servidor web. Por ejemplo, podríamos
imprimir el contenido HTML de la
página:
>>>> printU
respuesta.read()
<html>....
No pongo aquí la información devuelta
porque podría ocupar una página com-
pleta. Además es de poca utilidad. Lo
interesante de mechanize es que genera
la página y nos permite acceder a las par-
tes «jugosas» de la misma de forma muy
sencilla. Digamos que queremos saber
qué formularios contiene la página:
>>> for form in br.forms():
... print form
...
<POST http://U
www.linux-magazine.es/Readers/U
Newsletter/reply application/U
x-www-form-urlencoded
<HiddenControlU
(subject=subscribe)U
(readonly)>
<TextControl(email=Tu email)>
<SubmitControl(<None>=OK)U
(readonly)>>
<POST http://U
www.linux-magazine.es/U
01 import re
02 import mechanize
03
04 br = mechanize.Browser()
05
06 br.set_handle_robots(False)
07
08 respuesta =
br.open(“http://www.linux-
magazine.es/”)
09
10 br.select_form(nr=1)
11
12 br[“words”]=”python”
13
14 br.submit()
15
16 # Estamos en la página de
resultados
17
18 #primer resultado
19 urls = [url.absolute_url for
url in
br.links(url_regex=re.com-
pile(r”pdf$”))]
20
21 #eliminamos duplicados
22
23 urls =
dict(zip(urls,urls)).keys()
24
25
26 r =
re.compile(“.*/(\d+)/(.*)$”)
27
28
29 for url in urls:
30
31 m= r.match(url)
32 nombre =
m.group(1)+”-”+m.group(2)
33 print nombre
34
35 respuesta = br.open(url)
36 datos = respuesta.read()
37
38 fichero = open (nombre,’w’)
39 fichero.write(datos)
40 fichero.close()
Listado 1: Nuestra Araña web
LIBRERÍASMechanize
75PYTHONW W W. L I N U X - M A G A Z I N E . E S
search application/U
x-www-form-urlencoded
<TextControl(words=)>>
Mechanize tiene su propio lenguaje para
representar partes de la página, y aquí
podemos ver un ejemplo del mismo.
Nos dice que hay dos formularios, que
emplean POST como método de comuni-
cación con la página. Muy bien, noso -
tros queremos acceder al segundo
puesto, que es el que emplea la url
http://www.linuxmagazine.es/s earch.
Este formulario tiene un campo de texto
llamado words. Al principio cuesta un
poco entender este lenguaje, pero con
un poco de práctica no es tan compli-
cado.
De acuerdo, tenemos que acceder al
segundo formulario, así que le indicamos
a br, nuestro navegador virtual, que
emplee este formulario. Es posible realizar
la selección por posición o por nombre. El
nombre vendría indicado por el parámetro
HTML name, que el desarrollador de la
web de Linux Magazine ha decidido igno-
rar, al fin y al cabo no es obligatorio.
Si se diese el caso de que el formulario
tuviese un nombre, podríamos seleccio-
narlo con:
>>> br.select_formU
(name = “miformulario”)
Pero como no es este nuestro caso, lo
seleccionaremos por posición:
>>> br.select_form(nr = 1)
Empleamos el número 1, porque los for-
mularios están numerados comenzando
en 0. Con este método ya tenemos selec-
cionado el formulario, pero éste se com-
pone a su vez de varios elementos
incrustados. ¿Cómo podemos seleccio-
narlos? Por suerte para noso tros, John, el
desarrollador, ha empleado toda la
potencia de Python y ha realizado un
truco de magia: hacer que el objeto
almacenado en br se comporte como un
tipo diccionario Python. Si el elemento
input donde hay que escribir las palabras
a buscar se llama “words”, entonces
todo lo que tenemos que hacer es:
>>> br[“words”]=”python”
¡Así de simple! ¿Es o no maravilloso este
módulo? Esto sí que es código compacto
en estado puro. Después de la euforia
debemos volver al asunto que nos ha tra-
ído hasta aquí: queremos los enlaces con
la palabra “python”. Ya tenemos nuestro
formulario relleno, ahora debemos pul-
sar el botón. ¿Pero cómo? Pues con el
método submit:
>>> respuesta = br.sumbit()
Lo que acabamos de hacer es más com-
plejo de lo que parece, ¿qué ha ocurrido?
Al ejecutar submit hemos enviado los
datos del formulario al servidor, que nos
habrá respondido redirigiéndonos a la
página con los resultados. El contenido
de esta interacción se almacena en res-
puesta, que no es ni más ni menos que
otro objeto que envuelve un documento
HTML. Debemos recoger todos los enla-
ces que nos interesan.
Y aquí viene otro punto fuerte de
mechanize: su integración con las expre-
siones regulares. Desde luego que John
no nos iba a fallar en este aspecto. Pode-
mos elegir un enlace usando una expre-
sión regular, de forma que no tenemos
que ir buscando a tontas y a locas. Con
saber más o menos qué formato tendrá
el enlace que deseamos, podremos con-
seguir la información que contiene. Pero
antes veamos qué deberíamos hacer si
Figura 1: Página de resultados de búsqueda de Linux Magazine.
LIBRERÍAS Mechanize
76 PYTHON W W W. L I N U X - M A G A Z I N E . E S
no supiéramos muy bien qué buscamos.
Al igual que con los formularios, los
enlaces se pueden recorrer como si fue-
sen una lista:
>>> for link in br.links():
... print link
...
Link(base_url=’http://U
www.linux-magazine.es/’,U
url=’/’,text=’logoOL.gif[IMG]’,U
tag=’a’, attrs=[(‘href’, ‘/’)])
Link(base_url=’http://U
www.linux-magazine.es/’,U
url=’/’,text=’logoOR.gif[IMG]’,U
tag=’a’, attrs=[(‘href’, ‘/’)])
....
Aquí sólo se muestran los dos primeros.
Si pruebas esto mismo en tu equipo
verás que hay un número respetable de
enlaces en esta página en concreto. De
nuevo mechanize nos muestra lo que
entiende por un enlace. Pero como noso -
tros sabemos lo que queremos, podemos
pasar directamente a la acción con las
expresiones regulares:
>>> urls =U
[url.absolute_url for url inU
br.links(url_regex=re.compileU
(r”pdf$”))]
Python comprime en poco código mucho
trabajo, así que esta única línea requiere
una explicación. Comencemos por la
lista de compresión. En Python es posi-
ble crear listas a partir de definiciones de
lo que se supone que va en las mismas.
En este caso hay que comenzar por el
código:
br.links(url_regex=U
re.compile(r”pdf$”))
Este código localiza todos aquellos enla-
ces que se puedan identificar con el
argumento que pasemos al método
br.links(). Es posible usar un número,
como hicimos con el formulario ante-
riormente, pero en lugar de eso vamos a
emplear una expresión regular. Así que
usamos la función re.compile(), que no
hace otra cosa que generar un objeto
que contiene un reconocedor de la
expresión regular que pasamos como
parámetro. En nuestro caso es “pdf$”,
(la r de delante le indica a la función
que la cadena se corresponde con una
expresión regular), que no hace otra
cosa que localizar cadenas acabadas en
la letras “pdf”, y para ello podemos el
símbolo “$” al final de “pdf”. Como
decíamos, re.compile() devuelve un
objeto que reconoce la expresión regu-
lar, lo que podríamos llamar una expre-
sión regular compilada, y lo almacena-
mos en el argumento con nombre
url_regex. La función br.links() lo reco-
noce y sabe que debe buscar enlaces
que al reconocerlos con la expresión
regular devuelvan True.
br.links() generará así una lista de
enlaces, y aquí entra en función la cláu-
sula for ... in ..., que no hace otra cosa
que recorrer la lista y devolver los enla-
ces bajo el nombre de variable url. La
lista de compresión se compone de cada
uno de esos enlaces, con nombre url, de
lo que nos quedamos con su atributo
absolute_url: su ruta completa. Y con
esto acabamos. Todo se reduce a una
sola línea de Python, ¡escribimos poco
pero vale por decenas de líneas!
Aún tenemos un problema, la web de
resultado de búsqueda de Linux Maga-
zine devuelve los resultados duplicados.
Aplicando un poco de Kung Fu Python
podemos deshacernos de ellos en una
línea:
>>> urls = dict(U
zip(urls,urls)).keys()
Zip significa cremallera en inglés, y eso
es precisamente lo que hace la función
zip(). Cierra dos listas como si fuese una
cremallera:
>>> zip([“uno”,”dos”,U
“tres”],[1,2,3])
[(“uno”,1),(“dos”,2),U
(“tres”,3)]
Esto puede resultar muy conveniente,
porque precisamente una lista con tuplas
de 2 valores es lo que necesitamos para
crear un diccionario.
>>> dict(zip([“uno”,”dos”,U
“tres”],[1,2,3]))
{‘dos’: 2, ‘tres’: 3,U
‘uno’: 1}
Y del diccionario podemos obtener las
llaves usando el método keys(). Si hace-
mos todo esto con un lista, cerrándola
con ella misma en cremallera, y teniendo
en cuenta que en un diccionario no pue-
den existir dos llaves iguales, el resul-
tado es que al extraer las llaves obtene-
mos la misma lista pero eliminando los
duplicados.
¿Complicado? Sí, como las líneas ante-
riores, pero indudablemente útil y que
requiere muy pocas pulsaciones del
teclado.
De acuerdo, ya tenemos los enlaces…
Y ¿qué hacemos con ellos ahora? Pues
podríamos descargarlos: recorriendo la
lista y usando la función br.open() para
cargarlos, y la respuesta.read() para leer
el contenido del fichero, guardándolos
en un fichero con nombre igual a la
última parte de la URL. Por desgracia,
muchos de ellos se llaman
“Python.pdf”, así que vamos a usar el
número de esa revista en el nombre.
Puedes ver el código completo en el Lis-
tado 1.
Pero, con este código sólo podemos
conseguir los primeros resultados ¿no?
Sí, para mejorarlo y que descargue todos
los resultados sólo tendríamos que loca-
lizar el enlace a «siguiente resultado» y
pulsar en él con el método
br.follow_link(). Dejo al lector que
piense cómo hacerlo, dando una sola
pista: emplea un bucle hasta que no
encuentres links que se correspondan
con la expresión regular.
ConclusiónNo es de extrañar que Google utilice
Python. De hecho, el propio Guido Van
Rossum creó una araña web con una de
las primeras implementaciones de
Python ya hace algunos añitos. Hemos
podido comprobar cómo podemos usar
una página web como si estuviésemos
delante de un navegador mediante la
magnífica, y aún en estado Beta, librería
mechanize, y empleando un número de
líneas de código realmente minúsculo.
La próxima vez que el lector se enfrente
a un trabajo tedioso con una página web,
puede que tenga un par de ideas para
hacer que el ordenador trabaje por él
gracias a cierta serpiente. ■
[1] Mechanize para Perl: http:// search.
cpan. org/ dist/ WWW-Mechanize/
[2] Mechanize para Python: http://
wwwsearch. sourceforge. net/
mechanize/
Recursos
La biblioteca ReportLab crea directa-
mente documentos PDF basándose en
comandos gráficos y sin pasos interme-
dios, generando informes en un tiempo
extremadamente rápido y
siendo de gran utilidad en
los siguientes contextos:
generación dinámica de
PDFs en aplicaciones web
(empleado con Zope),
generación de informes y
publicación de datos
almacenados en bases de
datos, embebiendo el
motor de impresión en
aplicaciones para conse-
guir la generación de
informes a medida, etc.
Primeros PasosLo primero es tener instalados Python y
ReportLab para realizar todas las prue-
bas que van surgiendo y las que se nos
ocurran. En [1] se detallan los pasos
que hay que seguir para instalar y confi-
gurar ReportLab.
El paquete pdfgen es el nivel más bajo
para generar documentos PDF, que se
basa esencialmente en
una secuencia de instruc-
ciones para “dibujar” cada
página del documento. El
objeto que proporciona las
operaciones de dibujo es
el Canvas. El Canvas mide
igual que una hoja de
papel blanco, con puntos
sobre la misma identifica-
dos mediante coordenadas
cartesianas (X,Y), que por
defecto tienen el origen
(0,0) en la esquina infe-
rior izquierda de la pági-
na. La coordenada X va
hacia la derecha y la coordenada Y
avanza hacia arriba (ver Figura 1).
Para crear nuestro primer PDF basta
escribir en un fichero, que podemos lla-
mar ejemplo1.py, las siguientes líneas
de código:
from reportlab.pdfgenU
import canvas
c=canvas.Canvas("primer.pdf")
c.drawString(50,500, " MiU
PRIMER PDF")
c.drawString(250,300,U
"Coordenada=(250,300) ")
c.drawString(350,200,U
"(350, 10)")
c.drawString(150,400,U
"Aprendiendo REPORTLAB")
c.showPage()
c.save()
Probamos el programa y vemos que en
el mismo directorio ya se ha creado un
fichero llamado primer.pdf, análogo al
que se muestra en la Figura 2, sin nece-
sidad de realizar ningún otro paso inter-
medio. Mediante la línea from repor-
tlab.pdfgen import canvas importamos
LIBRERÍASReportLab
77PYTHONWWW. L I NUX - MAGAZ INE . ES
Figura 1: Coordenadas carte-
sianas de una hoja.
Hoy en día se hace imprescindible disponer de herramientas que permitan generar informes en PDF de alta
calidad rápida y dinámicamente. Existen diferentes herramientas para esta finalidad, entre ellas cabe destacar
ReportLab, biblioteca gratuita que permite crear documentos PDF empleando como lenguaje de programa-
ción Python. Por Ana M. Ferreiro y José A. García
Generación de informes profesionales desde Python
ReportLab
LIBRERÍAS ReportLab
78 PYTHON WWW. L I NUX - MAGAZ INE . ES
Canvas, utilizado para dibujar en el
PDF. El comando canvas.Canvas
(path__fichero) permite indicar el nom-
bre con el que se guardará el PDF. El
método draw String(x,y,cadena_texto)
empieza a escribir el texto en la coorde-
nada (x,y) (se puede probar a cambiar
las diferentes coordenadas). El método
showPage() crea la página actual del
documento. Finalmente, save() guarda
el fichero en el path indicado.
En el ejemplo previo hemos creado
un PDF sin especificar el tamaño del
documento, si queremos fijar el tamaño
de la hoja (A4, letter, A5, etc.) bastaría
indicarlo en el Canvas mediante:
from reportlab.lib.U
pagesizes import letter,A4,A5,U
A3
c=canvas.Canvas("primer.pdf",U
pagesize=letter)
El tamaño de las hojas se importa
mediante from reportlab. lib.pagesizes
import letter, A4,A5,A, y se especifica
en el Canvas con la propiedad pagesize.
Muchas veces querremos
adaptar el dibujo a las
dimensiones de la hoja, por
lo que necesitamos conocer
el ancho y el alto de la
misma: ancho= tipo
_hoja[0] y alto=tipo_hoja
[1]; donde tipo_ hoja puede
ser letter, A4, A5 , etc.
La clase Canvas dispone
de diferentes herramientas
para dibujar líneas, circun-
ferencias, rectángulos,
arcos, etc. Además permite
modificar el color de los
objetos, rotar, trasladar,
indicar tipo y tamaño de fuente, etc. En
el código del Listado 1 podemos ver
cómo se dibujan, por ejemplo líneas,
mediante canvas.line (x1,y1,x2 ,y2);
círculos, empleando el método
canvas.circle (x_centro,y_ centro, radio,
stroke=1, fill=1); y rectángulos con
esquinas redondeadas, con canvas.
round Rect (x,y, ancho, alto ,angulo,
stroke =1,fill=0). Nótese que cada vez
que se quiera emplear un color nuevo
hay que indicarlo mediante canvas
setFillColor RGB(r,g,b), para el color de
relleno, o canvas. setStroke ColorRGB
(r,g,b), para fijar el color de las líneas.
La elección del tipo de fuente se realiza
usando canvas .SetFont (tipo_ fuente,
tamaño). En la Figura 3 se muestra el
PDF que creamos con el simple código
del Listado 1. Podemos probar a cam-
biar propiedades (en el manual de
ReportLab encontraremos muchas
otras).
Añadiendo ImágenesEn este instante ya podemos demostrar
nuestra creatividad en dibujo “artísti-
co”, aunque de un modo bastante labo-
rioso. Seguro que más de uno preferi-
mos incluir en nuestros ficheros imáge-
nes ya creadas. Pues esto es posible: en
el área de descarga de Linux Magazine
tenemos la imagen Tux2.png para las
diferentes pruebas.
A la hora de incluir imágenes pode-
mos optar por las siguientes opciones.
La primera y más sencilla, pero con la
que no nos es posible rotar, trasladar, ni
redimensionar es mediante el método
drawImage (image,x,y,width =None,
height=None) de la clase Canvas (si no
se especifica el alto y el ancho, coloca la
Figura 2: Primer documento generado. El resultado impreso
refleja cómo controlar las coordenadas de una hoja.
01 from reportlab.pdfgen import
canvas
02 c=canvas.Canvas
("canvas_draw.pdf")
03 c.setFont("Helvetica",24)
04 c.line(50,50,50,350)
05 c.line(50,50,350,50)
06 c.setStrokeColorRGB(1,1,0.0)
07 c.setFillColorRGB(0,0.0,0.5)
08 c.roundRect
(75,75,275,275,20,stroke=0,
fill=1)
09 c.setFillColorRGB(0.8,0.,0.2)
10 c.circle (205,205,100,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()
Listado 1: ejemplo2.py
Figura 3: Objetos que se pueden dibujar con un Canvas.
LIBRERÍASReportLab
79PYTHONWWW. L I NUX - MAGAZ INE . ES
figura con sus dimensiones originales).
Mediante las siguientes líneas podemos
crear un fichero similar al de la Figura 4.
c.drawImage("Tux2.png",0,0)
c.drawImage("Tux2,png",200,300,U
width=30,height=60)
Si lo que pretendemos es rotar imágenes
o escalarlas, debemos emplear los obje-
tos Image(x,y,ancho,alto,path_imagen)
y Drawing(ancho,alto) que se importan
mediante from reportlab.graphics.shapes
import Image, Drawing. El objeto Dra-
wing puede escalarse, rotarse y trasla-
darse; pero hay que tener en cuenta que
todas estas operaciones son acumulati-
vas (ver Figura 5). En el Listado 2 pode-
mos ver cómo emplear correctamente
estos objetos (Figura 6). Obsérvese que
ahora el PDF no se genera a partir de un
Canvas, sino que se genera mediante
renderPDF.drawToFile (d,"canvas_
image 2.pdf"), donde d=Drawing
(A4[0] ,A4[1]). Podemos probar a modi-
ficar los valores de los distintos métodos
scale, rotate, translate; observaremos
que a veces la imagen puede desapare-
cer del folio, eso es debido a que los
valores que se dan hacen que nos salga-
mos de las dimensiones de la página.
Creación de Párrafos y TablasLa clase reportlab.platypus.Paragraph
permite escribir texto formateado (justifi-
cado, alineado a la derecha o izquierda,
centrado) en un aspecto elegante, ade-
más de modificar el estilo y color de tro-
zos de la línea a través de XML. Mediante
Paragraph(texto,style, bullet Text=None)
se instancia la clase; texto contiene el
texto del párrafo, en el que se eliminan
los espacios en blanco innecesarios;
bulletText indica si el párrafo se escribe
con un punto al inicio del mismo; la
fuente y otras propiedades del párrafo y
el punto se indican mediante el argumen-
to style. Veamos cómo añadir un párrafo:
01 from reportlab.lib.styles
import getSampleStyleSheet
02 styleSheet=getSampleStyle
Sheet()
03 story=[]
04 h1=styleSheet['Heading1']
05 h1.pageBreakBefore=0
06 h1.keepWithNext=1
07 h1.backColor=colors.red
08 P1=Paragraph("Estilo Cabecera
- h1 ",h1)
09 story.append(P)
10 style=styleSheet['BodyText']
11 P2=Paragraph("Estilo
BodyText"
,style)
12 story.append(P2)
El paquete reportlab.lib.styles contiene
estilos predefinidos. Con getSample
Style Sheet obtenemos un estilo ejem-
plo. Tenemos un estilo para la cabecera
y otro para el texto normal. Mediante
h1.pageBreakBefore=0 decimos que no
queremos un salto de página cada vez
que se escriba una cabecera h1, en caso
contrario basta escribir 1. Los diferentes
párrafos se van almacenando en la lista
story porque posteriormente se añaden
al pdf a través el paquete SimpleDoc
Template de reportlab.platypus:
doc=SimpleDocTemplate(U
"paragrahp.pdf", pagesize=A4,U
showBoundary=1)
doc.build(story)
En este caso se genera un PDF con tan-
tas páginas como sea necesario.
Figura 4: Colocando imágenes con
drawImage.
Figura 5: Modo en que se rota, traslada y
escala un objeto Drawing.
Figura 6: Ejemplo de rotación, traslación y
escalado de imágenes.
01 from reportlab.graphics.shapes
import Image, Drawing
02 from reportlab.graphics import
renderPDF
03 from reportlab.lib.pagesizes
import A4
04 inpath="Tux2.png"
05 IMAGES=[]
06 d=Drawing(80,100)
07 img=Image(200,0,80,100,inpath)
08 d.add(img)
09 d.rotate(45)
10 d.scale(1.5,1.5)
11 IMAGES.append(d)
12 d=Drawing(80,100)
13 d.add(img)
14 d.translate(10,0)
15 d.scale(2,2)
16 d.rotate(-5)
17 IMAGES.append(d)
18 d=Drawing(A4[0],A4[1])
19 for img in IMAGES:
20 d.add(img)
21 renderPDF.drawToFile(d,"can-
vas_image2.pdf")
Listado 2: Jugando con Imagenes (ejemplo3_2.py)
LIBRERÍAS ReportLab
80 PYTHON WWW. L I NUX - MAGAZ INE . ES
Comprobar esto es tan sencillo como
tener un texto muy largo. Si añadimos
un párrafo cuyo texto sea "Hola"*300,
seguro que se generan más de una
hoja.
Si lo que queremos es añadir una
tabla, es necesario importar from repor-
tlab .platypus import Table, TablsStyle.
Una tabla se crea añadiendo una lista de
listas, donde cada componente de la
lista guarda la información de cada fila.
Si queremos construir una tabla de 5
filas y 3 columnas hacemos
t=Table([['','Ventas',U
'Compras'],U
['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
números 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 código del Listado 3 mostramos
un ejemplo. En él se ilustra cómo
adaptar el estilo según se quiera
(Figura 7). Podemos ver que para
incluir un nuevo elemento en el PDF
es suficiente con ir añadiendo cada
objeto a la lista story.
Ahora ya sabemos todo lo necesario
para crear nuestros propios carteles,
informes, catálogos, presentaciones,
etc. ■
Figura 7: Ejemplo de párrafo, tabla e imagen.
01 from reportlab.lib.pagesizes
import A4
02 from reportlab.lib.styles
import getSampleStyleSheet,
ParagraphStyle
03 from reportlab.platypus import
Spacer,
SimpleDocTemplate, Table,
TableStyle
04 from reportlab.platypus import
Paragraph, Image
05 from reportlab.lib import
colors
06
07 styleSheet=
getSampleStyleSheet()
08 story=[]
09 h1=styleSheet['Heading1']
10 h1.pageBreakBefore=0
11 h1.keepWithNext=1
12 h1.backColor=colors.red
13 h2=styleSheet['Heading2']
14 h2.pageBreakBefore=0
15 h2.keepWithNext=1
16 P=Paragraph("Estilo Cabecera -
h1 ",h1)
17 story.append(P)
18 P=Paragraph("Estilo h2 ",h2)
19 story.append(P)
20 style=styleSheet['BodyText']
21 texto=" Texto escrito para ver
como crear ficheros PDF."+\
22 "Este parrafo esta escrito
en estilo BodyText"
23 texto_largo=texto
24 #texto_largo=texto*100
25 P=Paragraph(texto_largo,style)
26 story.append(P)
27 story.append(Spacer(0,12))
28
29 t=Table([['','Ventas',
'Compras'],
30 ['Enero',1000, 2000],
31 ['Febrero',3000,100.5],
32 ['Marzo',2000,1000],
33 ['Abril',1500,1500]]
34 )
35
36 story.append(t)
37
38 story.append(Spacer(0,15))
39 P=Paragraph("Cabecera h1",h1)
40 story.append(P)
41
42 cadena='''Mediante ReportLab es
43 posible generar ficheros PDF
44 de gran calidad. Es posible
45 incluir graficos, imagenes,
46 tablas; creando informes
47 de gran calidad'''
48 P=Paragraph(cadena,style)
49
50 story.append(Spacer(0,15))
51
52 img=Image ("Tux2.png",
width=80,height=100)
53 story.append(img)
54 doc=SimpleDocTemplate("para-
grahp.pdf",pagesize=A4,showBoun
dary=1)
55 doc.build(story)
Listado 3: Crear Tablas y Párrafos (ejemplo4.py)
[1] Reportlab:
http:// www. reportlab. org
Recursos
SERVICIO Autores / Contacto
82 PYTHON WWW. L I NUX - MAGAZ INE . ES
Ana María Ferreiro 65, 75
Jose Antonio García 65, 76
José María Ruíz 6, 15, 10, 19, 23, 28, 33, 39, 43, 48, 55, 59, 69, 73
Frank Wiles 52
Paul C. Brown 3, 10
Pedro Orantes 28, 33, 59, 69
DirectorPaul C. Brown
CoolaboradoresPaul C. Brown, José María Ruíz
TraductoresPaqui Martín Vergara, Lucas González, VíctorTienda
MaquetaciónMiguel Gómez Molina
Diseño de PortadaPaul C. Brown
Publicidadwww.linux-magazine.es/ pub/
Para EspañaMarketing y Comunicaciones [email protected].: (+ 34) 952 020 242Fax.: (+ 34) 951 235 905
Para el Resto del MundoPetra [email protected] Tel.: (+49) 8999 34 11 23Fax.: (+49) 8999 34 11 99
Director EditorialPaul C. Brown
Jefe de ProducciónMiguel Gómez Molina
Subscripciones:www.linux-magazine.es/ magazine/subsPrecios Subscripción España: 54,90 €Europa: 64,90 €Resto del Mundo - Euros: 84,90 €[email protected].: (+34) 952 020 242Fax.: (+34) 951 235 905
Linux MagazineLinux New Media Spain, S.L.Edfco. Hevimar, Planta 2, Ofic. 16
C/Graham Bell nº 629590 - Málaga ESPAÑA [email protected].: (+34) 952 020 242
(+34) 951 235 904Fax.: (+34) 951 235 905
www.linux-magazine.es - Españawww.linux-magazine.com - Mundowww.linux-magazine.co.uk - Reino Unidowww.linux-magazine.com.br - Brasilwww.linux-magazine.pl - Polonia
Si bien se toman todas las medidas posibles para garantizar la precisión del contenido de los artículospublicados en Linux Magazine, la editorial no se hace responsable de imprecisiones aparecidas en larevista. Asimismo, Linux Magazine no comparte necesariamente las opiniones vertidas por sus colaboradores en sus artículos. 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 yerrores.
Copyright y Marcas Registradas © 2012 Linux NewMedia Spain, S.L. Linux New Media Spain S.L. prohíbe la reproducción total o parcial de los con-tenidos de Linux Magazine sin su permiso previo ypor escrito. Linux es una Marca Registrada de LinusTorvalds.
Autores Datos de Contacto