O módulo curses permite criar interfaces de texto avançadas em terminais de células, com janelas, cores, menus, teclado, mouse, atualização parcial e adaptação ao tamanho da tela. Ele usa a biblioteca curses/ncurses e evita que a aplicação precise escrever sequências ANSI específicas para cada terminal.
É uma boa escolha para dashboards locais, monitores de processos, instaladores, gerenciadores de arquivos, ferramentas administrativas e aplicações executadas por SSH. Para um programa confiável, é essencial restaurar o terminal após erros, respeitar as capacidades informadas pelo terminfo e tratar resize, Unicode e terminais pequenos.
Disponibilidade
curses é um módulo opcional disponível normalmente em Unix. Não é suportado em Android, iOS ou WASI. No Windows, pode ser necessário instalar uma implementação compatível de terceiros.
try:
import curses
except ImportError:
curses = None
Uma aplicação deve oferecer mensagem clara ou modo alternativo quando curses não estiver disponível.
Comece com wrapper()
curses.wrapper() inicializa a biblioteca, configura cbreak, desliga echo, ativa o keypad, inicializa cores quando disponíveis e restaura o terminal ao sair, inclusive se a função principal gerar exceção.
import curses
def main(stdscr):
stdscr.clear()
stdscr.addstr(0, 0, "Olá, terminal")
stdscr.refresh()
stdscr.getch()
curses.wrapper(main)
Prefira wrapper() a chamar manualmente initscr() e endwin(). O traceback aparecerá depois que o terminal já estiver em estado normal.
Coordenadas
Curses usa coordenadas (y, x): linha primeiro, coluna depois. O canto superior esquerdo é (0, 0).
altura, largura = stdscr.getmaxyx()
centro_y = altura // 2
centro_x = largura // 2
stdscr.addstr(centro_y, max(0, centro_x - 5), "Centralizado")
Confundir x e y é uma causa comum de escrita fora da janela.
Evite escrever fora dos limites
addstr() e addch() geram curses.error ao ultrapassar a janela. Escrever exatamente no canto inferior direito também pode gerar exceção depois de desenhar o caractere.
def escrever_seguro(win, y, x, texto, attr=0):
altura, largura = win.getmaxyx()
if not (0 <= y < altura and 0 <= x < largura):
return
disponivel = max(0, largura - x - 1)
if disponivel:
win.addnstr(y, x, texto, disponivel, attr)
Calcule o espaço disponível e trate telas pequenas como estado normal.
Loop de eventos
def main(stdscr):
stdscr.keypad(True)
while True:
stdscr.erase()
stdscr.addstr(0, 0, "Pressione q para sair")
stdscr.refresh()
tecla = stdscr.getch()
if tecla in (ord("q"), ord("Q")):
break
Não execute tarefas longas dentro do loop sem permitir atualização e cancelamento.
Teclas especiais
Com keypad(True), sequências do terminal são traduzidas para constantes como KEY_UP, KEY_DOWN, KEY_LEFT e KEY_RIGHT.
if tecla == curses.KEY_UP:
selecao = max(0, selecao - 1)
elif tecla == curses.KEY_DOWN:
selecao = min(len(itens) - 1, selecao + 1)
Não presuma que toda tecla existe. has_key() e as capacidades do terminal ajudam a detectar suporte.
getch(), getkey() e get_wch()
getch() retorna inteiro. getkey() retorna string ou nome de tecla. get_wch() é mais adequado a Unicode: retorna um caractere para texto e inteiro para teclas especiais.
entrada = stdscr.get_wch()
if isinstance(entrada, str):
processar_texto(entrada)
elif entrada == curses.KEY_RESIZE:
redesenhar = True
Para interfaces internacionais, prefira get_wch() e configure locale corretamente.
Entrada não bloqueante
nodelay(True) faz getch() retornar -1 quando não há entrada. timeout(ms) espera por um período limitado.
stdscr.timeout(100)
while True:
tecla = stdscr.getch()
atualizar_metricas()
if tecla == ord("q"):
break
Não crie busy loop com timeout zero sem pausa ou trabalho útil.
halfdelay()
halfdelay(tenths) espera entre 0,1 e 25,5 segundos por uma tecla e gera erro se nada chegar. Para aplicações comuns, window.timeout() costuma ser mais simples.
Escape e sequências
set_escdelay(ms) controla quanto tempo curses aguarda depois de ESC para distinguir a tecla Escape de uma sequência de função. O valor precisa equilibrar resposta local e conexões SSH lentas.
Janelas
newwin() cria regiões independentes.
altura, largura = stdscr.getmaxyx()
menu = curses.newwin(altura - 2, 30, 1, 0)
conteudo = curses.newwin(altura - 2, largura - 30, 1, 30)
menu.box()
conteudo.box()
Subjanelas compartilham memória com a janela pai. Mudanças e refresh precisam ser coordenados.
Pads
newpad() cria uma área maior que a tela, útil para logs ou documentos extensos. O refresh de um pad recebe coordenadas da região virtual e da tela.
pad = curses.newpad(1000, 200)
for indice in range(1000):
pad.addstr(indice, 0, f"Linha {indice}")
pad.refresh(offset, 0, 1, 0, altura - 2, largura - 1)
Valide todos os retângulos; dimensões incompatíveis geram curses.error.
Atualizar várias janelas
Chamadas repetidas a refresh() podem aumentar flicker. Use noutrefresh() em cada janela e uma única chamada a doupdate().
menu.noutrefresh()
conteudo.noutrefresh()
curses.doupdate()
Essa técnica sincroniza a tela física depois que todas as janelas atualizaram o estado virtual.
Cores
Chame start_color() e verifique 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))
O número de cores e pares está em COLORS e COLOR_PAIRS após a inicialização.
Cores padrão no Python 3.14
assume_default_colors(fg, bg), adicionado no Python 3.14, permite usar o foreground e background padrão do terminal e pode preservar transparência.
if hasattr(curses, "assume_default_colors"):
curses.assume_default_colors(-1, -1)
curses.init_pair(1, curses.COLOR_CYAN, -1)
Tenha fallback para versões anteriores e terminais sem esse recurso.
Atributos visuais
Constantes como A_BOLD, A_REVERSE, A_UNDERLINE e A_DIM podem ser combinadas com cores.
Nem todo terminal suporta todos os atributos. Não dependa apenas de cor para comunicar estado; use texto, símbolos e posição.
Unicode e largura visual
O número de caracteres Python não é necessariamente o número de células. Ideogramas podem ocupar duas colunas e caracteres combinantes zero.
Para alinhamento internacional robusto, use uma biblioteca de cálculo de largura como wcwidth e teste emojis e caracteres combinados.
Encoding da janela
Cada janela possui atributo encoding, normalmente baseado na locale. Configure a locale antes de inicializar curses.
import locale
locale.setlocale(locale.LC_ALL, "")
Não altere locale global durante o loop; ela afeta todo o processo.
Redimensionamento
O terminal pode retornar KEY_RESIZE. Consulte novamente getmaxyx(), recrie layout e desenhe tudo.
if tecla == curses.KEY_RESIZE:
curses.update_lines_cols()
stdscr.erase()
reconstruir_layout(stdscr)
resizeterm() e resize_term() atualizam estruturas internas. Pads precisam de tratamento próprio.
Tela mínima
Defina dimensões mínimas e mostre mensagem simples quando a janela for pequena.
altura, largura = stdscr.getmaxyx()
if altura < 10 or largura < 40:
escrever_seguro(stdscr, 0, 0, "Aumente o terminal")
stdscr.refresh()
return
Mouse
mousemask() ativa eventos. Depois de KEY_MOUSE, use getmouse().
Suporte varia por terminal, multiplexador e SSH. Sempre ofereça navegação por teclado.
Campo de texto
curses.textpad.Textbox oferece edição de texto com comandos semelhantes ao Emacs. Valide o tamanho e normalize o conteúdo ao terminar.
No Python 3.14, o limite máximo de getstr() e instr() aumentou de 1023 para 2047 caracteres, mas isso não elimina a necessidade de limitar entrada.
Erros de escrita
Algumas operações geram curses.error em terminais pequenos ou coordenadas no limite. Trate erros esperados localmente, mas não ignore toda exceção.
Um except curses.error: pass global pode esconder bugs de layout.
Terminal e terminfo
Curses consulta a base terminfo indicada por TERM. Um valor incorreto, como declarar xterm-256color em terminal incompatível, produz teclas ou cores erradas.
Não defina TERM arbitrariamente. Corrija o ambiente ou instale a entrada terminfo adequada.
Não escreva sequências ANSI manualmente
Misturar escapes diretos com curses pode desalinhar o estado virtual e físico. Use as funções da biblioteca para cursor, atributos e limpeza.
Subprocessos
Antes de iniciar um programa interativo externo, salve e restaure o modo shell com def_prog_mode(), endwin() e reset_prog_mode(), ou encerre a interface e recrie-a.
Para automação isolada, use o guia de pty no Python.
Relação com tty e termios
Curses gerencia cbreak, echo, keypad e detalhes de terminal sobre as APIs explicadas em tty no Python e termios no Python.
Não altere flags termios por fora enquanto curses está ativo sem compreender como restaurar o estado interno.
Threads
Mantenha todas as chamadas curses em uma única thread. Workers podem produzir dados por fila; a thread da interface consome e desenha.
Isso evita concorrência sobre o terminal e simplifica resize e shutdown.
Shutdown cooperativo
Use uma flag ou evento para encerrar o loop e deixe wrapper() restaurar o terminal.
Não faça logging complexo em signal handler. O artigo de signal no Python mostra como acordar o loop principal.
Segurança
Não exiba texto não confiável sem remover caracteres de controle. Dados remotos podem conter escape, newline, tab e sequências que deformam a interface.
def limpar_texto(valor):
return "".join(ch if ch.isprintable() else "?" for ch in str(valor))
Também limite tamanho para impedir consumo excessivo e operações de desenho muito caras.
Testes
Teste terminais pequenos, resize contínuo, ausência de cor, 8 e 256 cores, Unicode, SSH, tmux, screen, teclado diferente, mouse ausente, exceção durante desenho e encerramento por Ctrl+C.
Use pty para testes de integração, mas faça testes manuais em terminais reais. O rendering exato depende da implementação curses e do terminfo.
Arquitetura recomendada
Separe estado, comandos e renderização. O loop lê uma tecla, converte em ação, atualiza o modelo e redesenha a partir do modelo. Evite lógica de negócio espalhada por chamadas addstr().
Essa estrutura facilita testes sem curses e evita inconsistência visual.
Erros comuns
Os erros mais frequentes são inicializar sem wrapper(), escrever fora da janela, presumir largura igual a len(), depender de cor, ignorar resize, usar busy loop, misturar ANSI direto, chamar curses de várias threads e imprimir texto remoto com controles.
Conclusão
curses permite construir interfaces de terminal portáveis e eficientes sobre as capacidades reais do sistema. Use wrapper(), valide dimensões, coordene refresh, trate Unicode e resize e mantenha a UI em uma única thread.
Teste com terminais e terminfo reais e ofereça fallback quando o módulo não existir. Consulte a documentação oficial de curses e o manual ncurses(3X).







