python power 1

84
WWW.LINUX - MAGAZINE.ES 8 413042 594529 00006 BÁSICO Introducción a Python con ejemplos prácticos Denis Babenko - 123RF.com MANUAL PRÁCTICO DVD GRATIS Debian 6 17 PROYECTOS COMPLETOS Y FUNCIONALES Tu Guía Práctica Aprende de desarrolladores profesionales 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 INFRAESTRUCTURAS Explota los motores tras Facebook, Google y Twitter 48 MÓDULOS Descubre cómo programar para 3D y a manipular datos e imágenes 59

Upload: manuel-velasco

Post on 14-Aug-2015

41 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: 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

Page 2: Python power 1

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.

Page 3: Python power 1

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

Page 4: Python power 1

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

Page 5: Python power 1

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

Page 6: Python power 1

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

Page 7: Python power 1

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

Page 8: Python power 1

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

Page 9: Python power 1

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

Page 10: Python power 1

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

Page 11: Python power 1

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

Page 12: Python power 1

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

Page 13: Python power 1

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

Page 14: Python power 1

• 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.)

Page 15: Python power 1

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

Page 16: Python power 1

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

Page 17: Python power 1

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.

Page 18: Python power 1

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.

Page 19: Python power 1

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

Page 20: Python power 1

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.

Page 21: Python power 1

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.

Page 22: Python power 1

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.

Page 23: Python power 1

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

Page 24: Python power 1

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»

Page 25: Python power 1

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.

Page 26: Python power 1

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

Page 27: Python power 1

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

Page 28: Python power 1

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

Page 29: Python power 1

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

Page 30: Python power 1

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

Page 31: Python power 1

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

Page 32: Python power 1

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

Page 33: Python power 1

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

Page 34: Python power 1

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.

Page 35: Python power 1

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

Page 36: Python power 1

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.

Page 37: Python power 1

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.

Page 38: Python power 1

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

Page 39: Python power 1

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

Page 40: Python power 1

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.

Page 41: Python power 1

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

Page 42: Python power 1

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.

Page 43: Python power 1

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

Page 44: Python power 1

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.

Page 45: Python power 1

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.

Page 46: Python power 1

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.

Page 47: Python power 1

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.

Page 48: Python power 1

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

Page 49: Python power 1

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

Page 50: Python power 1

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

Page 51: Python power 1

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

Page 52: Python power 1

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

Page 53: Python power 1

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

Page 54: Python power 1

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

Page 55: Python power 1

“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

Page 56: Python power 1

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

Page 57: Python power 1

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

Page 58: Python power 1

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

Page 59: Python power 1

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

Page 60: Python power 1

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.

Page 61: Python power 1

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.

Page 62: Python power 1

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

Page 63: Python power 1

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

Page 64: Python power 1

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.

Page 65: Python power 1

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.

Page 66: Python power 1

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

Page 67: Python power 1

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.

Page 68: Python power 1

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.

Page 69: Python power 1

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

Page 70: Python power 1

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

Page 71: Python power 1

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

Page 72: Python power 1

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

Page 73: Python power 1

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

Page 74: Python power 1

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

Page 75: Python power 1

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.

Page 76: Python power 1

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

Page 77: Python power 1

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

Page 78: Python power 1

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.

Page 79: Python power 1

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)

Page 80: Python power 1

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

Page 81: Python power 1
Page 82: Python power 1

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

Page 83: Python power 1
Page 84: Python power 1