curses en Python: interfaces de terminal

Publicado el: 26/08/2026
Tempo de leitura: 6 minutos
A male software engineer working on code in a modern office setting.

El módulo curses permite crear interfaces de texto avanzadas en terminales de celdas, con ventanas, colores, menús, teclado, mouse, actualización parcial y adaptación al tamaño de pantalla. Utiliza curses o ncurses en lugar de secuencias ANSI escritas manualmente.

Es una buena opción para dashboards locales, monitores de procesos, instaladores, gestores de archivos, herramientas administrativas y aplicaciones usadas por SSH. Para que el programa sea confiable, debe restaurar el terminal después de errores, respetar las capacidades de terminfo y manejar Unicode, resize y pantallas pequeñas.

Disponibilidad

curses es un módulo opcional disponible normalmente en Unix. No está soportado en Android, iOS ni WASI. En Windows puede ser necesaria una implementación compatible de terceros.

try:
    import curses
except ImportError:
    curses = None

La aplicación debería ofrecer un modo alternativo o un mensaje claro cuando curses no exista.

Comienza con wrapper()

curses.wrapper() inicializa la biblioteca, activa cbreak, desactiva echo, habilita keypad, inicializa colores si están disponibles y restaura el terminal al salir, incluso cuando la función principal lanza una excepción.

import curses


def main(stdscr):
    stdscr.clear()
    stdscr.addstr(0, 0, "Hola, terminal")
    stdscr.refresh()
    stdscr.getch()

curses.wrapper(main)

Prefiere wrapper() a llamar manualmente initscr() y endwin(). El traceback aparece después de restaurar el terminal.

Coordenadas

Curses usa (y, x): fila primero y columna después. La esquina superior izquierda es (0, 0).

altura, ancho = stdscr.getmaxyx()
y = altura // 2
x = ancho // 2
stdscr.addstr(y, max(0, x - 5), "Centrado")

Confundir x e y suele provocar escrituras fuera de la ventana.

Limita cada escritura

addstr() y addch() generan curses.error al dibujar fuera de la ventana. Escribir en la celda inferior derecha también puede lanzar una excepción después de pintar.

def escribir_seguro(win, y, x, texto, attr=0):
    altura, ancho = win.getmaxyx()
    if not (0 <= y < altura and 0 <= x < ancho):
        return
    disponible = max(0, ancho - x - 1)
    if disponible:
        win.addnstr(y, x, texto, disponible, attr)

Trata un terminal pequeño como un estado normal, no como un crash.

Loop de eventos

def main(stdscr):
    stdscr.keypad(True)
    while True:
        stdscr.erase()
        stdscr.addstr(0, 0, "Pulsa q para salir")
        stdscr.refresh()

        tecla = stdscr.getch()
        if tecla in (ord("q"), ord("Q")):
            break

No ejecutes trabajo largo dentro del loop sin permitir redraw y cancelación.

Teclas especiales

Con keypad(True), las secuencias del terminal se convierten en constantes como KEY_UP, KEY_DOWN, KEY_LEFT y KEY_RIGHT.

if tecla == curses.KEY_UP:
    seleccion = max(0, seleccion - 1)
elif tecla == curses.KEY_DOWN:
    seleccion = min(len(elementos) - 1, seleccion + 1)

No dependas de una tecla poco común como única forma de acceder a una función.

getch(), getkey() y get_wch()

getch() devuelve un entero. getkey() devuelve texto o nombre de tecla. get_wch() es mejor para Unicode: devuelve un carácter para texto y un entero para teclas especiales.

valor = stdscr.get_wch()
if isinstance(valor, str):
    procesar_texto(valor)
elif valor == curses.KEY_RESIZE:
    necesita_redibujar = True

Configura locale antes de inicializar curses.

Entrada no bloqueante

nodelay(True) hace que getch() devuelva -1 si no hay entrada. timeout(ms) espera un tiempo limitado.

stdscr.timeout(100)
while True:
    tecla = stdscr.getch()
    actualizar_metricas()
    if tecla == ord("q"):
        break

No crees busy loop con timeout cero y sin pausa o trabajo útil.

halfdelay()

halfdelay(tenths) espera entre 0,1 y 25,5 segundos y genera error si no llega una tecla. window.timeout() suele integrarse mejor.

Tiempo de Escape

set_escdelay(ms) controla cuánto espera curses después de ESC para distinguir Escape de una secuencia de función. Ajusta el valor para terminal local y SSH lento.

Ventanas

newwin() crea regiones independientes.

altura, ancho = stdscr.getmaxyx()
menu = curses.newwin(altura - 2, 30, 1, 0)
contenido = curses.newwin(altura - 2, ancho - 30, 1, 30)
menu.box()
contenido.box()

Las subventanas comparten memoria con sus padres, por lo que el orden de dibujo y refresh debe coordinarse.

Pads

newpad() crea un área mayor que la pantalla, útil para logs o documentos.

pad = curses.newpad(1000, 200)
for indice in range(1000):
    pad.addstr(indice, 0, f"Línea {indice}")
pad.refresh(offset, 0, 1, 0, altura - 2, ancho - 1)

Valida tanto el rectángulo virtual como el visible.

Actualizar varias ventanas

Múltiples refresh() pueden aumentar flicker. Usa noutrefresh() en cada ventana y una sola llamada a doupdate().

menu.noutrefresh()
contenido.noutrefresh()
curses.doupdate()

Colores

Llama start_color() y comprueba has_colors().

if curses.has_colors():
    curses.start_color()
    curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK)
    stdscr.addstr(0, 0, "OK", curses.color_pair(1))

COLORS y COLOR_PAIRS están disponibles después de inicializar colores.

Colores predeterminados en Python 3.14

assume_default_colors(fg, bg), añadido en Python 3.14, permite utilizar foreground y background predeterminados y conservar transparencia.

if hasattr(curses, "assume_default_colors"):
    curses.assume_default_colors(-1, -1)
    curses.init_pair(1, curses.COLOR_CYAN, -1)

Incluye fallback para versiones anteriores y terminales sin soporte.

Atributos visuales

A_BOLD, A_REVERSE, A_UNDERLINE y A_DIM pueden combinarse con colores.

No comuniques estado solo por color. Algunos terminales no soportan todos los atributos y algunos usuarios no distinguen ciertos colores.

Unicode y ancho visual

La longitud de un string Python no siempre coincide con las celdas visibles. Ideogramas pueden ocupar dos columnas y marcas combinantes cero.

Usa una biblioteca como wcwidth para alineación robusta y prueba emojis y caracteres combinados.

Encoding de la ventana

Cada ventana posee encoding, normalmente derivado de la locale.

import locale
locale.setlocale(locale.LC_ALL, "")

Configura locale una vez antes de curses, no durante el loop.

Resize

Un cambio de tamaño puede producir KEY_RESIZE. Consulta dimensiones, reconstruye layout y redibuja desde el modelo.

if tecla == curses.KEY_RESIZE:
    curses.update_lines_cols()
    stdscr.erase()
    reconstruir_layout(stdscr)

resizeterm() actualiza ventanas estándar; los pads requieren lógica propia.

Dimensiones mínimas

Define un tamaño mínimo y muestra un mensaje simple cuando el terminal sea demasiado pequeño.

altura, ancho = stdscr.getmaxyx()
if altura < 10 or ancho < 40:
    escribir_seguro(stdscr, 0, 0, "Aumenta el terminal")
    stdscr.refresh()
    return

Mouse

mousemask() activa eventos. Después de KEY_MOUSE, llama getmouse().

El soporte cambia entre terminales, tmux, screen y SSH. Siempre ofrece navegación por teclado.

Campos de texto

curses.textpad.Textbox ofrece edición similar a Emacs. Valida y limita el contenido final.

Python 3.14 aumentó el máximo de getstr() e instr() de 1023 a 2047 caracteres, pero la aplicación debería imponer límites menores.

Manejo de curses.error

Pantallas pequeñas y escrituras en límites generan curses.error. Captura casos esperados localmente, pero evita un except curses.error: pass global.

TERM y terminfo

Curses utiliza la entrada terminfo seleccionada por TERM. Un valor incorrecto provoca teclas, colores y cursor rotos.

No fuerces xterm-256color sin comprobar el terminal. Corrige el entorno o instala la entrada terminfo correcta.

No mezcles ANSI directo

Escribir escapes manuales mientras curses está activo puede desincronizar la pantalla virtual y física. Usa las funciones de curses para cursor, atributos y limpieza.

Subprocesses

Antes de ejecutar un programa externo interactivo, restaura temporalmente el modo shell o cierra y recrea la interfaz.

Para automatización aislada consulta pty en Python.

Relación con tty y termios

Curses gestiona cbreak, echo, keypad y detalles sobre las APIs explicadas en tty en Python y termios en Python.

No cambies flags termios por fuera mientras curses está activo sin comprender su estado interno.

Threads

Mantén todas las llamadas curses en un solo thread. Workers pueden publicar datos por una cola y el thread de UI los dibuja.

Cierre cooperativo

Usa una flag o evento, abandona el loop normalmente y deja que wrapper() restaure el terminal.

Los signal handlers deben ser mínimos. signal en Python explica cómo despertar el loop.

Sanitizar texto no confiable

Datos remotos pueden contener controles, newlines, tabs y escapes.

def limpiar_texto(valor):
    return "".join(ch if ch.isprintable() else "?" for ch in str(valor))

Limita también el tamaño para evitar redraw costoso y memoria sin control.

Pruebas

Prueba terminal pequeño, resize continuo, ausencia de color, 8 y 256 colores, Unicode, SSH, tmux, screen, teclados distintos, mouse ausente, excepción durante dibujo y Ctrl+C.

Usa pty para integración, pero verifica manualmente en terminales reales porque terminfo y curses cambian.

Arquitectura recomendada

Separa estado, comandos y render. Lee una tecla, conviértela en acción, actualiza el modelo y redibuja desde ese modelo. No mezcles lógica de negocio con llamadas addstr().

Errores comunes

Los fallos frecuentes son omitir wrapper(), dibujar fuera de ventanas, asumir que len() es ancho visual, depender solo de color, ignorar resize, usar busy loop, mezclar ANSI, llamar curses desde varios threads y mostrar controles no confiables.

Conclusión

curses crea interfaces de terminal portables y eficientes basadas en las capacidades reales del sistema. Usa wrapper(), valida dimensiones, agrupa refresh, maneja Unicode y resize y conserva la UI en un thread.

Prueba con terminales y terminfo reales y ofrece fallback cuando curses no esté disponible. Consulta la documentación oficial de curses y ncurses(3X).

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Rack de servidores que representa el balanceo de conexiones con SO_REUSEPORT_LB en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    SO_REUSEPORT_LB: reparte conexiones entre workers

    Aprende SO_REUSEPORT_LB en Python para distribuir conexiones entre workers con pruebas, portabilidad y cierre ordenado.

    Ler mais

    Tempo de leitura: 5 minutos
    11/10/2026
    Código Python asíncrono en un portátil para inspect.markcoroutinefunction
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    markcoroutinefunction: detecta wrappers async

    Aprende inspect.markcoroutinefunction en Python para identificar wrappers asíncronos, integrar frameworks y evitar detecciones incorrectas.

    Ler mais

    Tempo de leitura: 5 minutos
    10/10/2026
    Código Python para recorrer carpetas y archivos con Path.walk
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    Path.walk: recorre directorios con seguridad

    Aprende Path.walk en Python para recorrer directorios, filtrar archivos, tratar errores y controlar la travesía con seguridad.

    Ler mais

    Tempo de leitura: 5 minutos
    10/10/2026
    Depuración de un proceso Python en terminal con código
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pdb -p: depura procesos Python en ejecución

    Aprende a conectar pdb a un proceso Python en ejecución, inspeccionar la pila y diagnosticar bloqueos de forma segura.

    Ler mais

    Tempo de leitura: 6 minutos
    09/10/2026
    Código Python para representar fracciones exactas
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    fractions.from_number: convierte números en fracciones

    Aprende fractions.from_number en Python para convertir números en fracciones exactas, controlar precisión, validar entradas y evitar redondeos inesperados.

    Ler mais

    Tempo de leitura: 4 minutos
    09/10/2026
    Desarrollador configurando un servidor HTTPS y certificado TLS con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crea un servidor HTTPS local en Python

    Aprende HTTPSServer en Python para crear servicios HTTPS locales, configurar certificados, usar hilos y comprender sus límites.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026