Um console interativo precisa decidir se o texto digitado já forma uma instrução Python completa. Depois de if condicao:, por exemplo, ele deve mostrar o prompt de continuação em vez de tentar executar imediatamente. O módulo codeop no Python fornece as funções usadas para compilar entradas de REPL, distinguir código completo de código incompleto e preservar instruções __future__ entre comandos.
Neste guia, você aprenderá a usar compile_command(), Compile e CommandCompiler, tratar erros e montar um loop interativo controlado. O conteúdo complementa nossos artigos sobre bytecode com dis, escopos com symtable, traceback, inspect e Python IDLE.
O problema de um REPL
O ciclo read-eval-print lê texto, compila, executa e mostra o resultado. A dificuldade é que uma linha pode ser válida, inválida ou apenas incompleta.
if usuario_ativo:
liberar_acesso()Depois da primeira linha, o parser espera um bloco indentado. Um console precisa responder com ..., acumular mais texto e tentar novamente.
compile_command()
codeop.compile_command() tenta compilar uma string como entrada interativa.
import codeop
resultado = codeop.compile_command("x = 10")
print(resultado)Quando o texto está completo e correto, a função retorna um objeto de código. Quando é um prefixo válido, mas incompleto, retorna None. Sintaxe inválida gera uma exceção.
Distinguir incompleto de inválido
import codeop
entradas = [
"x = 10",
"if x > 0:",
"if :",
]
for texto in entradas:
try:
codigo = codeop.compile_command(texto)
except SyntaxError as erro:
print("Inválido:", erro)
else:
if codigo is None:
print("Incompleto")
else:
print("Completo")Essa diferença é a principal razão para usar codeop em vez de chamar apenas compile().
O parâmetro filename
O nome do arquivo aparece em tracebacks e mensagens de sintaxe.
codigo = codeop.compile_command(
"resultado = 10 / 0",
filename="<console-admin>",
)Escolha um identificador que indique a origem, como <console>, <regra-42> ou o caminho real de um script. Não inclua segredos ou dados pessoais no filename.
O parâmetro symbol
symbol controla o modo de compilação:
single: uma instrução interativa, valor padrão;exec: sequência de instruções;eval: uma expressão.
expressao = codeop.compile_command(
"10 * 2",
symbol="eval",
)
print(eval(expressao, {}))Qualquer outro valor gera ValueError. Escolha o modo de acordo com a interface e não permita que o usuário o controle livremente sem validação.
single e exibição de resultados
O modo single foi projetado para comportamento interativo. Expressões podem ser enviadas ao display hook do interpretador, semelhante ao console padrão.
codigo = codeop.compile_command("2 + 3", symbol="single")
exec(codigo)Em uma aplicação web, provavelmente será melhor capturar a saída e definir uma representação própria.
Construir um acumulador de linhas
Um loop simples mantém um buffer até o código ficar completo.
import codeop
buffer = []
while True:
prompt = "... " if buffer else ">>> "
linha = input(prompt)
buffer.append(linha)
fonte = "\n".join(buffer)
try:
codigo = codeop.compile_command(fonte)
except (SyntaxError, OverflowError, ValueError) as erro:
print(f"Erro: {erro}")
buffer.clear()
continue
if codigo is None:
continue
exec(codigo, globals(), globals())
buffer.clear()Esse exemplo demonstra a mecânica, mas executar entrada arbitrária com exec() é perigoso. Um console real precisa de isolamento e autorização.
Linhas em branco
No console tradicional, uma linha vazia encerra um bloco. Seu loop deve preservar o newline e permitir que compile_command() faça a decisão.
if buffer and linha == "":
fonte = "\n".join(buffer) + "\n"Teste funções, classes, try, with, decorators, strings multilinha e parênteses abertos.
Erros possíveis
A função pode gerar SyntaxError para sintaxe inválida, OverflowError ou ValueError para determinados literais e parâmetros.
try:
codigo = codeop.compile_command(fonte)
except SyntaxError as erro:
mostrar_syntax_error(erro)
except (OverflowError, ValueError) as erro:
mostrar_erro_compilacao(erro)A documentação oficial de codeop também alerta para casos raros em que o parser pode aceitar uma parte inicial e ignorar símbolos posteriores. Não use a função como validador de segurança.
CommandCompiler
CommandCompiler é um objeto chamável com interface semelhante a compile_command().
compilador = codeop.CommandCompiler()
codigo = compilador(
"x = 1",
filename="<sessao>",
symbol="single",
)A diferença importante é que a instância lembra instruções __future__ compiladas anteriormente.
Preservar __future__
Um REPL deve manter opções de compilação habilitadas pelo usuário para comandos seguintes.
compilador = codeop.CommandCompiler()
primeiro = compilador(
"from __future__ import annotations",
"<sessao>",
"single",
)
exec(primeiro, ambiente)
segundo = compilador(
"def processar(valor: TipoAindaNaoDefinido): pass",
"<sessao>",
"single",
)
exec(segundo, ambiente)Usar uma nova instância em cada entrada perderia esse estado. Mantenha um compilador por sessão.
A classe Compile
Compile se comporta de forma semelhante ao built-in compile() e também memoriza flags futuras.
compilador = codeop.Compile()
codigo = compilador(
"resultado = 2 + 2",
"<entrada>",
"exec",
)Compile é útil quando a aplicação já sabe que o código está completo. CommandCompiler acrescenta a detecção de entrada incompleta.
codeop e o módulo code
O módulo code oferece classes prontas para consoles e interpretadores interativos. A documentação oficial de code descreve InteractiveInterpreter e InteractiveConsole.
Use codeop diretamente quando precisa controlar protocolo, interface, armazenamento de sessão ou transporte. Para um console convencional embutido, as classes de code reduzem trabalho.
Capturar stdout e stderr
Uma interface gráfica ou remota precisa capturar saídas.
from contextlib import redirect_stdout, redirect_stderr
from io import StringIO
saida = StringIO()
with redirect_stdout(saida), redirect_stderr(saida):
exec(codigo, ambiente, ambiente)
print(saida.getvalue())Redirecionamento global não é seguro entre várias threads. Em serviços concorrentes, execute cada sessão em processo separado.
Ambiente de execução
Passar o mesmo dicionário como globals e locals preserva variáveis entre comandos.
ambiente = {
"__name__": "__console__",
}
exec(codigo, ambiente, ambiente)Esse dicionário não é uma sandbox. Mesmo removendo __builtins__, objetos disponíveis podem oferecer caminhos para filesystem, rede ou introspecção.
Não existe sandbox segura apenas com exec
Código Python arbitrário deve ser considerado equivalente a acesso ao processo. Ele pode ler arquivos, consumir CPU e memória, criar threads, abrir sockets e encerrar o programa.
Para entrada não confiável, use um processo ou container isolado, usuário sem privilégios, filesystem restrito, rede bloqueada, limites de CPU e memória, timeout e descarte completo após a execução.
Timeouts
Uma thread não consegue interromper de maneira segura qualquer código Python ou nativo. Execute a avaliação em subprocesso e finalize o processo ao exceder o prazo.
from subprocess import run, TimeoutExpired
try:
run(
["python", "runner_isolado.py"],
input=fonte,
text=True,
timeout=3,
check=True,
)
except TimeoutExpired:
print("Tempo excedido")O runner ainda precisa de limites do sistema operacional.
Sessões concorrentes
Cada sessão deve possuir buffer, CommandCompiler e namespace próprios. Não compartilhe o dicionário de globals entre usuários.
class Sessao:
def __init__(self):
self.buffer = []
self.compilador = codeop.CommandCompiler()
self.ambiente = {"__name__": "__console__"}Em aplicações distribuídas, armazene apenas código e resultados necessários. Objetos Python arbitrários não são serializáveis de forma segura.
Formatar SyntaxError
SyntaxError inclui filename, linha, offset, texto e mensagem.
except SyntaxError as erro:
print(erro.filename, erro.lineno, erro.offset)
print(erro.text)
print(erro.msg)O módulo traceback pode produzir uma representação consistente. Não exponha caminhos internos em interfaces públicas.
Auditoria
Um console administrativo deve registrar usuário, horário, origem, duração, status e um hash do código. Evite gravar segredos digitados acidentalmente e defina retenção.
Use autenticação forte, autorização explícita e aprovação adicional para ambientes críticos. A conveniência de um REPL remoto aumenta a superfície de ataque.
Testar o detector de completude
casos_incompletos = [
"if True:",
"def funcao(x):",
"(",
"'''texto",
]
for fonte in casos_incompletos:
assert codeop.compile_command(fonte) is NoneAdicione casos completos, inválidos, Unicode, decorators, comprehensions, match, async e sintaxe da versão suportada.
Compatibilidade entre versões
codeop usa o parser do interpretador atual. Uma entrada válida no Python 3.14 pode ser inválida no 3.11. O estado de __future__ também depende das features daquela versão.
Registre a versão da sessão e execute o código no mesmo runtime usado para validar.
Erros frequentes
- Confundir
Nonecom erro de sintaxe. - Criar novo
CommandCompilerpara cada linha. - Perder newlines ao acumular o buffer.
- Executar código de usuário no processo principal.
- Considerar remoção de built-ins uma sandbox.
- Compartilhar namespace entre sessões.
- Não impor timeout e limites de recursos.
- Expor tracebacks e caminhos internos.
Boas práticas
- Use um compilador por sessão.
- Trate completo, incompleto e inválido separadamente.
- Preserve linhas e filename coerentes.
- Prefira o módulo
codepara consoles convencionais. - Isole execução não confiável em processo ou container.
- Aplique limites de CPU, memória, rede e tempo.
- Audite consoles administrativos.
- Teste toda a sintaxe suportada.
Conclusão
O módulo codeop no Python resolve a parte delicada de um REPL: descobrir se a entrada já forma código completo e manter opções de compilação futuras entre comandos. compile_command() atende verificações pontuais, enquanto CommandCompiler preserva estado por sessão.
A compilação é apenas uma etapa. Executar o objeto resultante continua tão poderoso quanto executar um script. Ao separar análise de execução, isolar sessões, impor recursos e proteger a interface, você pode construir consoles, notebooks e ferramentas educacionais sem transformar conveniência em acesso irrestrito ao servidor.







