Contenido

Python curses

Python no deja de sorprenderme cada día. Y es increíble la cantidad de cosas que trae “de serie”. A veces me da la sensación de que le saco muy poco provecho.

Hace relativamente poco que me puse a pensar: Si casi todo lo que hago lo hago en consola, ¿por qué no hacerlo más bonito con curses? Pero no me decidía. El otro día decidí ponerme a ello, y he terminado con un sabor agridulce. Por un lado, he descubierto que se pueden hacer muchas cosas. Por otro, casi todas hay que hacerlas a mano.

Pero para que decidas por ti mismo, aquí tienes unos pasos básicos.

Python

Hello world

Una de las pocas cosas que han añadido en Python frente a la librería de C es un inicializador. Esto nos evita tener que estar inicializando el modo curses, deshabilitando la salida estándar, controlando errores para restaurar el sistema,… Ese tipo de tareas repetitivas. Así, usando este inicializador automático, el típico “hello world” queda así:

1
2
3
4
5
6
7
8
import curses

def main(stdscr):
    stdscr.addstr('hello world')
    stdscr.getch()

if __name__ == '__main__':
    curses.wrapper(main)

No tiene mayor misterio: curses.wrapper se encarga de inicializar todo lo inicializable y de controlar la salida del programa. Eso nos permite llamar a una función (main) que recibe como argumento la pantalla (stdscr por convención).

Podemos añadir texto en la pantalla con addstr y esperamos a que se pulse una tecla con getch.

No está mal, ¿no? Pero quiero un poco más. Quiero poder tratar bloques como un todo. Quiero moverlos.

Ventanas

Vamos a realizar lo mismo pero mediante ventanas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import curses

def main(stdscr):

    win = curses.newwin(1, 20, 10, 20);
    win.addstr('hello world')
    win.getch()

if __name__ == '__main__':
    curses.wrapper(main)

Aquí hay algo que contar, que son los parámetros de newwin: filas, columnas, posición Y, posición X. Resulta que a los autores de ncurses les pareció más lógico usar las coordenadas (Y, X) en lugar de las típicas (X, Y), y al hacer el wrapper para python no les pareció conveniente cambiarlo. Tenedlo en cuenta en todas vuestras coordenadas.

Vamos a darle movimiento al tema:

Movimiento

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import curses

def main(stdscr):

    win = curses.newwin(5, 20, 10, 20);
    win.addstr('hello world')
    win.getch()

    win.mvwin(15, 30)
    stdscr.refresh()

    win.getch()


if __name__ == '__main__':
    curses.wrapper(main)

Aquí sólo tengo que destacar que hay que hacer a mano el repintado de la pantalla. El modo curses está pensado para pantallas súper lentas. Por eso trata de optimizar recursos al máximo y espera que sea el programador quien decida que ha dejado de pintar y tiene que refrescar la pantalla.

Lo que pasa es que nuestras ventanas son feas. Me gustaría que tuvieran un borde, un título, esas cosas.

Bordes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import curses

def main(stdscr):

    win = curses.newwin(5, 20, 10, 20);

    win.box()
    win.move(0, 1)
    win.addstr('title')
    win.move(1, 1)
    win.addstr('hello world')

    win.getch()

    win.mvwin(15, 30)
    stdscr.refresh()

    win.getch()


if __name__ == '__main__':
    curses.wrapper(main)

Como ya dije: se hace todo a mano. Pintamos el borde, movemos el cursor un cuadro a la derecha (para respetar la esquina), escribimos el título, bajamos a la línea siguiente, y continuamos escribiendo.

Vaya mierda de código. Vamos a ponerlo bonito:

Modularizando

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import curses

def create_window(rows, cols, y, x, title):
    win = curses.newwin(rows, cols, y, x);

    win.box()
    win.move(0, 1)
    win.addstr('title')
    win.move(1, 1)
    return win


def main(stdscr):
    win = create_window(5, 20, 10, 10, 'title')
    win.addstr('hello world')
    win.getch()


    win.mvwin(15, 30)
    stdscr.refresh()

    win.getch()

if __name__ == '__main__':
    curses.wrapper(main)

Y de esta manera tenemos gráficos en consola :D

Vamos a utilizarlo en algo real, pidiendo algo:

Un ejemplo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import curses

def create_window(rows, cols, y, x, title):
    win = curses.newwin(rows, cols, y, x)

    win.box()
    win.move(0, 1)
    win.addstr(title)
    win.move(1, 1)
    return win

def create_centered_window(rows, cols, title):
    y = (curses.LINES - rows) // 2
    x = (curses.COLS - cols) // 2
    return create_window(rows, cols, y, x, title)

def get_name(win):
    curses.echo()
    result = win.getstr()
    curses.noecho()
    return result

def main(stdscr):
    win = create_centered_window(10, 40, 'Introduzca su nombre')
    name = get_name(win)
    win.erase()

    win = create_centered_window(5, 20, 'Saludo')
    win.addstr('hello, ' + name)

    stdscr.refresh()
    win.getch()

if __name__ == '__main__':
    curses.wrapper(main)

Aquí hay mogollón de nuevos conceptos que, si has conseguido llegar hasta aquí, no te costará entender :-P

De todas maneras, si tienes preguntas, pon un comentario.

Tío, vaya montón de mierda.

Pues sí. Hay que decirlo TODO. No hay nada automático.

Hay cosas que no hemos visto: gestión de colores, paneles, scroll,… Pero creo mi intención era adquirir un poco de culturilla general con esta librería y creo que está conseguido con creces.

Como véis, curses no está pensada para hacer aplicaciones, sino más bien scripts pequeños. Aún así, hay quien se ha currado una librería de widgets, con calendarios y esas cosas tan molonas, que se llama pycdk y que no es más que un wrapper sobre la CDK o bien la URWID, bastante más completa, y a la que podéis tener acceso desde python gracias a bpython-urwid . No dejéis de probar bpython. Es paquete debian y mola mucho.

El problema de este tipo de librerías es que perdemos algo importante: funcionar sólo con la API estándar de python.

De todas maneras, la API estándar tiene algunos widgets, como son el TextPad (un TextBox que soporta combinaciones de teclas de Emacs) y Panels, que son ventanas con profundidad.

Si queréis saber más, os remito al tutorial de Python curses o al propio manual de Python curses.