El módulo codeop ayuda a compilar código Python recibido de forma interactiva. Su capacidad principal es distinguir tres estados: código completo y válido, código definitivamente inválido y código incompleto que puede volverse válido cuando el usuario escriba más líneas. Esta decisión es esencial en REPLs, consolas embebidas, notebooks, editores educativos, shells administrativas y herramientas que ejecutan comandos multilínea.
Una llamada normal a compile() no está diseñada para decidir si un bloque solo necesita más entrada. codeop encapsula heurísticas compatibles con el comportamiento interactivo de Python y también puede recordar efectos de from __future__ entre comandos mediante CommandCompiler.
El problema del código incompleto
Considera una función escrita línea por línea:
def doble(valor):
return valor * 2
Después de la primera línea, la consola no debería rechazar el código; debería mostrar un prompt de continuación. Después del cuerpo y del terminador adecuado, puede compilar y ejecutar.
compile_command
compile_command(source, filename="<input>", symbol="single") determina el estado actual.
import codeop
resultado = codeop.compile_command("1 + 2")
print(resultado)
El código completo devuelve un code object. El código aparentemente incompleto devuelve None. El código definitivamente inválido lanza una excepción de sintaxis.
Maneja los tres resultados
def analizar(fuente):
try:
codigo = codeop.compile_command(fuente)
except (SyntaxError, OverflowError, ValueError) as error:
return "invalido", error
if codigo is None:
return "incompleto", None
return "completo", codigo
No trates None como error. Indica que la interfaz debe seguir recogiendo líneas.
El parámetro symbol
symbol controla el tipo de entrada esperado. Los valores comunes son single, exec y eval.
singlerepresenta entrada interactiva y puede activar la visualización de expresiones.execrepresenta un conjunto de instrucciones.evalacepta una expresión.
expresion = codeop.compile_command(
"10 * 4",
filename="<calculadora>",
symbol="eval",
)
Elige el modo conscientemente
Una consola tradicional suele usar single. Un editor de celdas puede usar exec. Una calculadora puede usar eval, aunque evaluar expresiones no confiables sigue siendo peligroso.
El modo define gramática y comportamiento del code object; no aporta seguridad.
Acumula líneas
Un REPL mínimo conserva un buffer hasta que la entrada queda completa.
import codeop
lineas = []
while True:
prompt = "... " if lineas else ">>> "
linea = input(prompt)
lineas.append(linea)
fuente = "\n".join(lineas)
try:
codigo = codeop.compile_command(fuente)
except SyntaxError as error:
print(f"error: {error}")
lineas.clear()
continue
if codigo is None:
continue
exec(codigo, namespace)
lineas.clear()
Una consola real también necesita políticas para EOF, KeyboardInterrupt, historial, encoding, salida y excepciones de ejecución.
Líneas en blanco
Las consolas interactivas usan líneas en blanco para finalizar ciertos bloques compuestos. Conserva esa semántica en lugar de borrar toda entrada vacía.
No apliques strip() al buffer completo porque puede alterar indentación y finalización.
Indentación
El whitespace inicial forma parte de la sintaxis. Conserva exactamente el texto introducido.
Un editor puede sugerir indentación, pero reescribir silenciosamente la fuente antes de compilar puede producir resultados inesperados.
SyntaxError
El código definitivamente inválido genera SyntaxError.
try:
codeop.compile_command("if :")
except SyntaxError as error:
print(error.msg, error.lineno, error.offset)
Muestra filename, línea, columna y un fragmento corto sin exponer buffers de otros usuarios.
Otros fallos de compilación
Entradas extremas pueden generar OverflowError, ValueError o fallos de recursos. Limita bytes, líneas, nesting y tiempo antes de aceptar fuente pública.
No aumentes automáticamente el límite de recursión ante código hostil o mal formado.
Filename simbólico
El argumento filename aparece en tracebacks y diagnósticos.
codigo = codeop.compile_command(
fuente,
filename="<consola-admin>",
)
Usa un nombre que identifique sesión o celda sin revelar datos personales. Un ID estable ayuda a recuperar el fuente correspondiente.
Integración con linecache
El código generado dinámicamente no tiene archivo normal. Para mostrar líneas correctas en tracebacks, conserva la fuente bajo su filename simbólico e intégrala cuidadosamente con el cache.
Consulta linecache en Python.
CommandCompiler
CommandCompiler es una alternativa con estado a llamar directamente a compile_command().
import codeop
compilador = codeop.CommandCompiler()
codigo = compilador("x = 10")
Está diseñado para comandos sucesivos pertenecientes a una misma sesión interactiva.
Declaraciones future
En un módulo, from __future__ import ... afecta el código posterior. En una consola, debería afectar comandos posteriores de la misma sesión.
CommandCompiler recuerda las flags observadas en compilaciones anteriores.
compilador = codeop.CommandCompiler()
compilador("from __future__ import annotations")
siguiente = compilador("def f(x: Tipo) -> Otro: pass")
Un compilador por sesión
No compartas un CommandCompiler entre usuarios independientes. Flags futuras y estado podrían filtrarse entre contextos.
Crea uno por consola, kernel, conexión o tenant.
Compilación frente a ejecución
Compilar valida sintaxis y crea bytecode; no ejecuta el cuerpo. La ejecución comienza con exec() o eval().
La compilación consume recursos, pero la ejecución introduce los riesgos mayores: filesystem, red, imports, subprocesses, introspección y código nativo.
No es un sandbox
codeop no restringe Python. Usar modo eval, reducir builtins o rechazar algunos nodos AST no crea un lenguaje seguro.
Para código no confiable, usa aislamiento fuerte: proceso separado, usuario sin privilegios, filesystem restringido, red controlada, límites de CPU y memoria y deadline duro.
Namespaces persistentes
Una consola suele conservar un diccionario de globals entre comandos.
namespace = {"__name__": "__console__"}
exec(codigo, namespace, namespace)
Esto permite persistir variables y funciones, pero también acumula memoria, archivos y referencias.
Separa usuarios
Cada usuario necesita su propio namespace. Compartir globals permite leer o modificar datos de otra sesión.
Destruye el proceso worker al terminar si necesitas cleanup fuerte. Threads y recursos nativos pueden sobrevivir al borrado del diccionario.
Visualización de expresiones
El modo single coopera con el mecanismo de display del intérprete. Una consola personalizada puede ajustar sys.displayhook.
Usa reprlib para limitar valores gigantes o recursivos. Consulta reprlib en Python.
Resultados grandes
Una expresión puede producir una representación enorme. Limita bytes, líneas y tiempo de render.
El método __repr__ de un objeto hostil puede ejecutar lógica arbitraria. No renderices objetos no confiables en un proceso privilegiado.
Captura de stdout y stderr
Las consolas web suelen capturar salida. contextlib.redirect_stdout() cambia estado global y no es seguro para múltiples sesiones concurrentes en un intérprete.
Un proceso worker por sesión ofrece ownership claro de streams, señales, límites y terminación.
Excepciones de ejecución
Después de compilar, captura errores en el boundary correcto.
try:
exec(codigo, namespace, namespace)
except SystemExit:
cerrar_sesion()
except Exception:
traceback.print_exc()
Define comportamiento explícito para KeyboardInterrupt, SystemExit y cancelación. Evita un handler que impida el shutdown de la aplicación.
Timeout de ejecución
Una thread no puede detener de forma segura cualquier código Python o nativo. Usa procesos descartables y termina el worker al vencer el plazo.
En sesiones internas confiables, un dump de faulthandler antes de terminar puede conservar contexto.
Async y top-level await
Una consola asíncrona puede querer await en nivel superior. Esto requiere flags de compilación y soporte del event loop más allá del comportamiento básico de codeop.
Usa APIs de la versión o framework interactivo objetivo.
Notebooks
Los kernels de notebook gestionan sesiones, historial, protocolos de display, IDs de celda, ejecución async, almacenamiento de fuente y salidas ricas. codeop resuelve únicamente completitud sintáctica y flags futuras.
Un loop de input() no es un kernel completo.
Consolas administrativas
Una consola embebida es una superficie de gestión de alto riesgo. Exige autenticación fuerte, autorización, auditoría, red privada y acceso temporal.
Prefiere comandos administrativos explícitos a ejecución arbitraria de Python.
Historial
Si almacenas comandos, cifra y define retención. Los usuarios pueden introducir tokens, datos personales y secretos accidentalmente.
Ofrece borrado y evita registrar sesiones sensibles por defecto.
Límites de entrada
Limita bytes, líneas, nesting y cuánto tiempo puede permanecer un buffer incompleto. Un cliente puede ocupar memoria y conexión indefinidamente.
Después del deadline, descarta el buffer o cierra la sesión.
Cancela el buffer actual
Ofrece un comando para limpiar solo el bloque sin terminar.
if linea == ":cancelar":
lineas.clear()
continue
Elige comandos fuera de la sintaxis habitual y documéntalos.
Autocomplete
La detección de completitud no ofrece autocomplete. Las sugerencias requieren tokens, AST parcial, symbol tables o un language server.
Evita evaluar descriptors y properties solo para descubrir atributos, porque pueden producir side effects.
Compatibilidad de versión
La gramática y heurísticas evolucionan. Prueba la consola en cada versión soportada.
Usa el codeop incluido con el intérprete en lugar de copiar lógica interna antigua.
Pruebas
Cubre expresiones, funciones y clases multilínea, decorators, paréntesis abiertos, strings triples, comprehensions, try/except, pattern matching, async, errores definitivos, EOF, interrupción y future imports.
Verifica también que sesiones independientes no compartan namespaces ni flags.
Errores comunes
Los fallos frecuentes son tratar None como error, eliminar indentación, elegir el symbol incorrecto, compartir CommandCompiler, ejecutar código hostil en el host, capturar streams globales, aceptar buffers ilimitados y confundir compilación con sandbox.
Conclusión
codeop aporta la lógica necesaria para decidir si una entrada interactiva está completa, incompleta o inválida. Usa compile_command() para comprobaciones sin estado y CommandCompiler para sesiones que conservan flags de __future__.
Separa namespaces, conserva fuente para tracebacks, impone límites y aísla la ejecución. Consulta la documentación oficial de codeop y symtable en Python para analizar nombres de una sesión.







