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

    A person reads 'Python for Unix and Linux System Administration' indoors.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    posix en Python: llamadas Unix directas

    Entiende posix en Python, llamadas Unix, descriptores, permisos, procesos, seguridad y cuándo usar os en lugar del módulo directo.

    Ler mais

    Tempo de leitura: 7 minutos
    26/08/2026
    Creative concept with coffee cup and paper question marks on a table.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    grp en Python: consulta grupos Unix

    Aprende grp en Python para consultar grupos Unix, GIDs, miembros, ownership, grupos suplementarios, NSS y containers.

    Ler mais

    Tempo de leitura: 5 minutos
    26/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pwd en Python: consulta usuarios Unix

    Aprende pwd en Python para consultar usuarios Unix por UID o login, obtener home, shell y ownership sin usar la

    Ler mais

    Tempo de leitura: 6 minutos
    26/08/2026
    Close-up of stacked logs showing natural textures and patterns, suitable for firewood or decor.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    syslog en Python: envía logs a Unix

    Aprende syslog en Python para enviar logs Unix con prioridades, facilities, máscaras, contenido estructurado y protección contra log injection.

    Ler mais

    Tempo de leitura: 4 minutos
    26/08/2026
    A CPU and RAM sticks displayed on a white surface, showcasing computer hardware components.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    resource en Python: límites de CPU y memoria

    Aprende resource en Python para medir CPU, pico de memoria y page faults, y limitar archivos, procesos, descriptors y address

    Ler mais

    Tempo de leitura: 5 minutos
    26/08/2026
    Close-up of a person using a metro card machine for public transport payment indoors.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pty en Python: automatiza terminales Unix

    Aprende pty en Python para ejecutar y probar programas interactivos, controlar pseudo-terminales, EOF, resize, señales y timeouts.

    Ler mais

    Tempo de leitura: 5 minutos
    25/08/2026