5/16/2018 Python Curses - slidepdf.com
http://slidepdf.com/reader/full/python-curses 1/6
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 pro-
grama tan raro que parece no servir para nada ? Yo tengo mala memoria, así que uso un cuaderno de bitá-
cora. POR JOSÉ MARÍA RUIZ Y PEDRO ORANTES
Cuaderno de bitácora, fecha estelar
2123….
En todos los libros sobre administra-
ción de sistemas se nos recomienda lle-
var un pequeño cuaderno de bitácora
donde ir reflejando las acciones peligro-
sas que realicemos. De esta manera, se
supone, podremos recrear paso a paso
los eventos que nos llevaron a un desas-
tre 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á-
bamos 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 mane-
ra que siempre pueda acceder a ellos.
¿Y qué ocurre si sólo lo queremos para
nosotros? ¿Y si la máquina a la que esta-
mos accediendo no tiene un servidor
web con el software adecuado configura-
do para tener un weblog? ¿y si no quere-
mos montar tanta parafernalia?
Algunas aplicaciones, como KPIM,
incorporan ya la opción de llevar un
diario personal, pero no funcionan de
forma remota a no ser que tengamos
una conexión de red con mucho ancho
de banda.
Python • DESARROLLO
61Número 11WWW.LINUX- M A G A Z I N E . E S 61
061-066_PythonL11 11.10.2005 9:54 Uhr Página 61
5/16/2018 Python Curses - slidepdf.com
http://slidepdf.com/reader/full/python-curses 2/6
una familia de librerías que
nos permiten almacenar
datos en un fichero y ges-
tionarlos como si fuesen
un diccionario o hash enPython. Cada entrada se
compone de una clave y
un valor asociado. Si no
tenemos que realizar
búsquedas complejas,
dbm se convertirá en
nuestro mejor opción.
Básicamente tenemos
que mostrar un interfaz que
divida la pantalla en dos partes.
En una deberá mostrar las fechas
almacenadas, y debe permitir reco-
rrerlas. En la otra debe mostrar el texto
relacionado con la fecha indicada.
Las acciones serán:
• Navegar entradas.
• Crear entrada.
• Editar entrada.
• Borrar entrada.
• Salir.
Cada una de las acciones se correspon-
derá 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 de 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 parecen 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 llaves y valores aso-
ciados a las mismas, así como recuperar
el valor o borrar las llaves, simplemente
eso.
La librería dbm necesita un fichero
donde depositar los datos que se almace-
nan. Así, tendremos que darle el nombre
de un fichero e indicarle cómo 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 sim-
ple. Se comporta como una lista, con todas
sus operaciones. El lector se habrá pregun-
tado al ver el código “¿dónde está el truco?
si dbm representa 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
“syntatic sugar”. Por traducirlo de alguna
manera, viene a decir que es una manera
de hacer agradable visualmente (y a
nuestros pobres dedos) la llamada a cier-
tas funciones del lenguaje.
¿Podemos incorporar “[]” a uno de
nuestro objetos y hacer que se comporte
como una lista? La respuesta es ¡si! 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)
¿Qué opcio-
nes nos quedan? Podemos volver nuestra
mirada a la era antigua de los ordenado-
res, 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 funcionan
mejor remotamente, aún con conexiones
lentas.
Vamos a diseñar y programar un cua-
derno de bitácora en Python, que utiliza-rá 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
abstraer, ocultar y simplificar la gestión
de terminales texto. Cada fabricante
dotaba a su terminal texto de caracterís-
ticas distintas a las del resto, forzadas la
mayoría de las veces por una feroz com-
petencia. Esto convertía en una tortura el
simple 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 progra-
mas sin tener en cuenta las diferencias
entre los terminales. No sólo eso, sino
que además simplificó enormemente la
gestión de interfaces de texto como vere-
mos 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
DESARROLLO • Python
62 Número 11 WWW.LINUX - M A G A Z I N E . E S
01 >>> import dbm
02 >>> datos = dbm.open('visitan-
tes','c') # crea el fichero
03 >>> datos["Juan Jose"] = "ven-
dra el martes"
04 >>> datos["Juan Jose"]
05 'vendra el martes'
06 >>> datos.close()
07 >>>
08 >>> datos = dbm.open('visitan-
tes')
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 mar-
tes
17 >>> datos.close()
Listado 1: Ejemplo de usode DBM
061-066_PythonL11 11.10.2005 9:54 Uhr Página 62
5/16/2018 Python Curses - slidepdf.com
http://slidepdf.com/reader/full/python-curses 3/6
Python • DESARROLLO
63Número 11WWW.LINUX- M A G A Z I N E . E S
un diccionario. Y precisamente eso es lo
que hacemos con nuestro objeto
Almacen que encubre un diccionario,
añadiendo nuevas 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 carac-
teres y poco más.
Debido a ello son bastante complica-
das de manejar. Hacer cosas vistosas
suele llevar mucho código. Por ello
nos vamos a centrar en un
interfaz sencillo.
Nuestro pro-
grama
será modal, tendrá un modo de “navega-
ció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 funciona-
miento. Por ello se pide al lector que eje-
cute todas las acciones relacionadas con
curses desde un programa Python ejecu-
table (recuerda hacer el chmod +x
<programa>). Podemos ver un progra-
ma que inicializa la consola con curses
en el Listado 3.
Posteriormente escribimos un “Hola
mundo” y se refresca la pantalla, pode-
mos ver el resultado en la Figura 1. Si no
refrescamos la pantalla curses no mostra-
rá nada. En el Listado 3 stdscr representa
toda la pantalla. Es posible crear subven-
tanas y hacer actualizaciones selectivas
como podremos comprobar en el código
del programa.
Una vez realizadas las operaciones,
pasamos a dejar la pantalla en una con-
figuración correcta, acción que reali-
zan las cuatro últimas llamadas a
funciones.
El objeto diario creará a suvez un objeto GUI , que ges-
tiona el interfaz, y el obje-
to Almacen que se
encarga de gestionar
la base de datos.
El objeto
Almacen es
pasado a
GUI
como
Estos cuatro métodos los enmascara
python posteriormente de la manera
mostrada en la Tabla 1. Por tanto pode-
mos enmascarar las acciones de un obje-
to de manera que se use como si fuese
Figura 1: Hola Mundo en nuestro primer pro-
grama curses.
01 #!/usr/local/bin/python
02
03 import dbm
04 class Almacen:
05 def __init__
(self,nombre):
06 self.bd =
dbm.open(nombre,'c')
07
08 def busca_palabra (self,
palabra):
09 claves =
self.entradas()
10 encontradas = []
11
12 for clave in claves:
13 contenido =
self.contenido(clave)
14 if palabra in
contenido:
15
encontradas.push(clave)
16
17 return encontradas18
19 def entradas (self):
20 a = self.bd.keys()
21 if not a:
22 a = []
23 return a
24
25 def cierra (self):
26 self.bd.close()
27
28 def __len__(self):
29 return
len(self.entradas())
30
31
32 def __setitem__ (self,
clave, valor):
33 self.bd[clave] = valor
34
35 def
__getitem__(self,clave):
36 return self.bd[clave]
37
38 def
__delitem__(self,clave):
39 del self.bd[clave]
Listado 2: almacen.py
061-066_PythonL11 11.10.2005 9:55 Uhr Página 63
5/16/2018 Python Curses - slidepdf.com
http://slidepdf.com/reader/full/python-curses 4/6
paráme-
tro en su crea-
ció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
tenemos seleccionada la distinguiremos
iluminándola en negrita y subrayándola.
La segunda ventana hará las veces de
barra de ayuda y estado. Cuando cam-
biemos 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 panta-
lla 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. El resto de 24 filas
se encargarán de mostrar las entradas
almacenadas.
Desplazamiento por lasentradasLa 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 decrementaremosuna variable que establece la posición la
entrada seleccionada en cada momento y
volveremos a dibujar, o escribir, la panta-
lla de datos. Pero no lo haremos de cual-
quier 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 seleccionar qué entradas
mostraremos.
Las listas en Python tienen una
funcionalidad que nos será muy
útil. Usando la sintaxis
lista[comienzo:fin] podemos
extraer los elementos entre comien-
zo y fin formando una nueva lista.
Simplemente tenemos que seleccionar
aquellos que estén a una distancia fija
del seleccionado y usarlos como
comienzo y fin.
Podemos ver el código que realiza esta
acción en el método dibuja_fechas(self)
de GUI en el Listado 6 (disponible en
[1]).
DESARROLLO • Python
64 Número 11 WWW.LINUX - M A G A Z I N E . E S
01 #!/usr/local/bin/python
02 # -*- coding: ISO8859-1 -*-
03
04 import curses
05 import curses.textpad
06
07 ncols, nlines = 9, 4
08 uly, ulx = 15, 20
09 stdscr.addstr(uly-2, ulx, "Use
Ctrl-G to end editing.")
10 win = curses.newwin(nlines,
ncols, uly, ulx)
11 rectangle(stdscr, uly-1,
ulx-1, uly + nlines, ulx +
ncols)
12 stdscr.refresh()
13 return Textbox(win).edit()
14
15 str =
curses.wrapper(test_editbox)
16 print 'Contents of text box:',
repr(str)
Listado 4: Ejemplo de usode Textbox
01 #!/usr/local/bin/python
02 # -*- coding: ISO8859-1 -*-
03
04 import curses
05
06 # Inicializamos la pantalla
07 stdscr=curses.initscr()
08 curses.noecho()
09 curses.cbreak()10 stdscr.keypad(1)
11
12 # Escribimos algo
13 stdscr.addstr("Hola mundo",0)
14 stdscr.refresh()
15
16 # Limpiamos la pantalla
17
18 stdscr.keypad(0)
19 curses.echo()
20 curses.nocbreak()
21 curses.endwin()
Listado 3: “Hola mundo”con curses
061-066_PythonL11 11.10.2005 9:55 Uhr Página 64
5/16/2018 Python Curses - slidepdf.com
http://slidepdf.com/reader/full/python-curses 5/6
Textbox
Python provee de una herramienta muy
útil para la edición de textos dentro de
curses. Desgraciadamente, a pesar de su
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.textpad. Textbox nos permite edi-
tar un texto dentro de una ventana y
poder utilizar muchas de las combina-
ciones 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óndebido 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
programa 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í nos hará un
buen servicio.
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
función rectangle de curses.textpad) y
nos solicita que escribamos algo en el
mismo. Para acabar debemos pulsar con-
trol+g , mostrándonos lo escrito más
abajo. Si lo probamos comprobaremos
que no podemos salir del rectángulo al
editar.
En la Figura 3 vemos cómo hemos
integrado el Textbox en nuestro progra-
ma. Hemos aumentado el número de
columnas para permitir introducir men-
sajes más largos.
El gestor de comandosExisten muchas maneras de hacer un
gestor de comandos. La más típica con-
Python • DESARROLLO
Figura 3: Inserción de una nueva entrada en
la bitácora.
Figura 2: Un cuadro de texto curses.
061-066_PythonL11 11.10.2005 9:55 Uhr Página 65
5/16/2018 Python Curses - slidepdf.com
http://slidepdf.com/reader/full/python-curses 6/6
Podemos invocar el método habla usan-
do una cadena con su nombre mediante
el método getattr(), que precisa de la ins-
tancia del objeto y el método a invocar.
Devuelve, por así decirlo, una referenciaal método en cuestión que funciona de la
misma manera. Como dicen por ahí,
“una imagen vale más que mil pala-
bras”:
>>> pepe = Persona()
>>> pepe.habla()
hola mundo
>>> (getattr(pepe, "habla"))()
hola mundo
>>>
Lo que haremos será crear una lista de
listas, cada una de las cuales contendrá
dos elementos. El primero será un carác-
ter y el segundo el nombre del método a
invocar. De esta manera, nuestro gestor
de comandos se reduce a un código que
recibe un carácter, lo compara con el pri-
mer elemento de cada entrada en su lista
de comandos y, si encuentra una coinci-
dencia, 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 coin-cidencia se ejecuta usando como instan-
cia self . De esta manera, es posible mani-
pular el funcionamiento según qué
caracteres responde el programa sin
tener que modificar el código fuente.
Esto nos da mucha flexibilidad y es
menos propenso a errores.
Uso del programaEl uso del programa se ha hecho lo más
simple posible, el aspecto del mismo
será el mostrado 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á introdu-
ciendo un texto, al pulsar “control+g”
se guarda. Para salir se pulsa “q” y con
los cursores “arriba” y “abajo” nos des-
plazamos por el programa.
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 de su
instalación base. Una vez realizado el
programa sabremos que cualquiera que
instale Python podrá hacer uso de él.
Siempre es posible realizar una serie
de objetos que lleven a cabo tareas demás alto nivel. Existen librerías que nos
proporcionan barras menús y widgets
más avanzados. Pero 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
fuese una buena idea darle una oportu-
nidad a curses. I
siste en hacer una sentencia switch o
gran cantidad de if s anidados, cada uno
de los cuales responde ante una tecla o
combinación distinta. El código genera-
do llega a convertirse en ilegible encuanto el número de comandos sobrepa-
sa 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 len-
guajes. Afortunadamente Python nos
permite una implementación muy senci-
lla. La idea es la siguiente.
Cada comando estará asociado a una
serie de acciones a realizar.
Englobaremos las acciones vinculadas
con cada comando a un método de nues-
tro objeto “GUI”. Hasta aquí todo es bas-
tante normal. Ahora viene la magia.
Python nos permite invocar métodos
de objetos usando su nombre. Si declara-
mos el objeto persona:
>>> class Persona:
... def habla(self):
... print "hola mundo"
...
>>>
DESARROLLO • Python
66 Número 11 WWW.LINUX - 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/11
RECURSOS
José María Ruiz actualmente está
realizando el Proyecto Fin de
Carrera de Ingeniería Técnica en
Informática de Sistemas mientras
estudia Física. Lleva 8 años usan-
do y desarrollando software libre
y, desde hace dos, se está especia-
lizando en FreeBSD. Pedro
Orantes está cursando 3º de
Ingeniería Técnica en Informática
de Sistemas y en sus ratos libres
toca en un grupo de música.
L O S
A U T O R E S
__len__(self) devuelve la longitud de
nuestro objeto. Se invoca cuando se eje-
cuta len(miObjeto)
__setitem(self,clave,valor) se correspon-
de con la asignación en un diccionario:
miObjeto["Algo"] = "otra cosa" __geti-
tem(self, clave) es el equivalente
miObjeto["Algo"] y devuelve la informa-
ción almacenada en “Algo”.
__delitem(self, clave) es del
miObjeto["Algo"] y se corresponde con
la eliminación de esa entrada.
TABLA 1: ALGUNOS MÉTODOSESPECIALES DE PYTHON
01 def ejecuta_commando(self,
ch):
02 "Procesa las teclas recibi-
das"
03 if curses.ascii.isprint(ch):
04 for comando in self.coman-
dos:
05 if comando[0] == chr(ch):
06 (getattr(self,coman-
do[1]))()
07 break
08 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étodoejecuta_comando(self,ch)
Figura 4: Vista de la bitácora con varias
entradas.
061-066_PythonL11 11.10.2005 9:55 Uhr Página 66