codeop en Python: compila entradas interactivas

Publicado el: 04/08/2026
Tempo de leitura: 7 minutos
Terminal de programación que representa compilación de entradas interactivas con codeop en Python

Una consola interactiva debe decidir si el texto introducido ya forma una instrucción Python completa. Después de if condicion:, por ejemplo, debería mostrar el prompt de continuación en lugar de intentar ejecutar inmediatamente. El módulo codeop en Python proporciona las utilidades de compilación usadas por los REPL para distinguir código completo de un prefijo válido pero incompleto y para conservar instrucciones __future__ entre comandos.

Esta guía explica compile_command(), Compile y CommandCompiler, incluido el manejo de errores y el diseño de un loop interactivo controlado. Complementa nuestros artículos sobre bytecode con dis, ámbitos con symtable, tracebacks, inspect y Python IDLE.

El problema que resuelve un REPL

Un ciclo read-eval-print lee texto, lo compila, lo ejecuta y muestra un resultado. La dificultad es que una línea puede estar completa, ser inválida o simplemente necesitar más líneas.

if usuario_activo:
    conceder_acceso()

Después de la primera línea, el parser espera un bloque indentado. Una consola debe responder con ..., acumular la entrada y volver a intentar la compilación.

compile_command()

codeop.compile_command() intenta compilar una cadena como entrada interactiva.

import codeop

resultado = codeop.compile_command("x = 10")
print(resultado)

Si el código está completo y es válido, devuelve un objeto de código. Si es un prefijo válido que todavía necesita contenido, devuelve None. Una sintaxis inválida provoca una excepción.

Distinguir incompleto de inválido

entradas = [
    "x = 10",
    "if x > 0:",
    "if :",
]

for texto in entradas:
    try:
        codigo = codeop.compile_command(texto)
    except SyntaxError as error:
        print("Inválido:", error)
    else:
        if codigo is None:
            print("Incompleto")
        else:
            print("Completo")

Esta distinción de tres estados es la razón principal para usar codeop en vez de llamar directamente al built-in compile().

El argumento filename

El nombre de archivo aparece en mensajes de sintaxis y tracebacks posteriores.

codigo = codeop.compile_command(
    "resultado = 10 / 0",
    filename="<consola-admin>",
)

Elige un identificador útil como <consola>, <regla-42> o la ruta real de un script. No incluyas secretos ni datos personales en el filename.

El argumento symbol

symbol selecciona el modo de compilación:

  • single: una instrucción interactiva, valor predeterminado;
  • exec: una secuencia de instrucciones;
  • eval: una expresión.
expresion = codeop.compile_command(
    "10 * 2",
    symbol="eval",
)

print(eval(expresion, {}))

Cualquier otro valor genera ValueError. La aplicación debe seleccionar el modo según su interfaz y no permitir que un cliente no confiable lo controle libremente.

single y la presentación del resultado

El modo single está pensado para comportamiento interactivo. Los resultados de expresiones pueden enviarse al display hook del intérprete, como en la consola estándar.

codigo = codeop.compile_command("2 + 3", symbol="single")
exec(codigo)

Una interfaz web o gráfica normalmente capturará la salida y aplicará su propia representación.

Construir un acumulador de líneas

Un loop sencillo mantiene un buffer hasta que el código queda completo.

import codeop

buffer = []

while True:
    prompt = "... " if buffer else ">>> "
    linea = input(prompt)
    buffer.append(linea)
    fuente = "\n".join(buffer)

    try:
        codigo = codeop.compile_command(fuente)
    except (SyntaxError, OverflowError, ValueError) as error:
        print(f"Error: {error}")
        buffer.clear()
        continue

    if codigo is None:
        continue

    exec(codigo, globals(), globals())
    buffer.clear()

El ejemplo demuestra la mecánica, pero ejecutar entrada arbitraria mediante exec() es peligroso. Una consola remota necesita aislamiento y autorización estricta.

Líneas en blanco

En el intérprete tradicional, una línea vacía finaliza un bloque indentado. Conserva los saltos de línea y deja que compile_command() tome la decisión.

if buffer and linea == "":
    fuente = "\n".join(buffer) + "\n"

Prueba funciones, clases, try, with, decorators, strings multilínea, paréntesis abiertos y bloques async.

Errores de compilación

La función puede lanzar SyntaxError por sintaxis inválida y OverflowError o ValueError por determinados literales y parámetros.

try:
    codigo = codeop.compile_command(fuente)
except SyntaxError as error:
    mostrar_error_sintaxis(error)
except (OverflowError, ValueError) as error:
    mostrar_error_compilacion(error)

La documentación oficial de codeop también advierte sobre casos poco comunes en los que el parser puede aceptar un prefijo válido antes de símbolos posteriores. No uses esta función como validador de seguridad.

CommandCompiler

CommandCompiler crea un objeto llamable con una interfaz similar a compile_command().

compilador = codeop.CommandCompiler()

codigo = compilador(
    "x = 1",
    filename="<sesion>",
    symbol="single",
)

La diferencia importante es que una instancia recuerda las instrucciones __future__ compiladas previamente.

Conservar el estado de __future__

Un REPL debe mantener activas las opciones futuras del compilador para los comandos siguientes.

compilador = codeop.CommandCompiler()
ambiente = {"__name__": "__console__"}

primero = compilador(
    "from __future__ import annotations",
    "<sesion>",
    "single",
)
exec(primero, ambiente)

segundo = compilador(
    "def procesar(valor: TipoTodaviaNoDefinido): pass",
    "<sesion>",
    "single",
)
exec(segundo, ambiente)

Crear un compilador nuevo para cada entrada perdería este estado. Mantén una instancia por sesión.

La clase Compile

Compile se comporta de forma parecida al built-in compile() y también recuerda flags futuras.

compilador = codeop.Compile()

codigo = compilador(
    "resultado = 2 + 2",
    "<entrada>",
    "exec",
)

Usa Compile cuando la aplicación ya sabe que la fuente está completa. CommandCompiler añade la detección de entrada incompleta.

codeop y el módulo code

El módulo code ofrece clases preparadas para intérpretes y consolas. La documentación oficial de code describe InteractiveInterpreter e InteractiveConsole.

Usa codeop directamente cuando un protocolo, interfaz, almacenamiento de sesión o transporte personalizado requiere control detallado. Para una consola embebida convencional, las clases de más alto nivel reducen boilerplate.

Capturar stdout y stderr

Una interfaz gráfica o remota necesita capturar la salida.

from contextlib import redirect_stdout, redirect_stderr
from io import StringIO

salida = StringIO()

with redirect_stdout(salida), redirect_stderr(salida):
    exec(codigo, ambiente, ambiente)

print(salida.getvalue())

La redirección global no es segura entre threads concurrentes. Ejecuta cada sesión remota en un proceso separado.

Namespaces de ejecución

Pasar el mismo diccionario como globals y locals conserva nombres entre comandos.

ambiente = {
    "__name__": "__console__",
}

exec(codigo, ambiente, ambiente)

Este diccionario no es una sandbox. Incluso reduciendo __builtins__, los objetos disponibles pueden ofrecer acceso al filesystem, red, imports o introspección.

exec no crea una sandbox segura

El código Python arbitrario debe considerarse equivalente a acceso al proceso. Puede leer archivos, consumir CPU y memoria, crear threads, abrir sockets y terminar el programa.

Para entrada no confiable, usa un proceso o container aislado, usuario sin privilegios, filesystem restringido, red bloqueada, límites de CPU y memoria, timeout y destrucción completa del entorno después de ejecutar.

Timeouts

Una thread no puede interrumpir de forma segura cualquier código Python o nativo. Ejecuta cada evaluación en un subprocess y termina el proceso cuando expire el plazo.

from subprocess import run, TimeoutExpired

try:
    run(
        ["python", "runner_aislado.py"],
        input=fuente,
        text=True,
        timeout=3,
        check=True,
    )
except TimeoutExpired:
    print("Tiempo excedido")

El runner todavía necesita límites de recursos del sistema operativo.

Sesiones concurrentes

Cada sesión debe tener su propio buffer, CommandCompiler y namespace. Nunca compartas un diccionario de globals entre usuarios.

class Sesion:
    def __init__(self):
        self.buffer = []
        self.compilador = codeop.CommandCompiler()
        self.ambiente = {"__name__": "__console__"}

En aplicaciones distribuidas, almacena solo la fuente y los resultados necesarios. Los objetos Python vivos no tienen una serialización general segura.

Formatear SyntaxError

SyntaxError contiene filename, línea, offset, texto fuente y mensaje.

except SyntaxError as error:
    print(error.filename, error.lineno, error.offset)
    print(error.text)
    print(error.msg)

El módulo traceback puede generar una representación consistente. Las interfaces públicas deben ocultar rutas internas.

Auditoría de consolas administrativas

Una consola administrativa debe registrar usuario autenticado, horario, origen, duración, estado, versión del runtime y un hash de la fuente enviada. Evita almacenar secretos introducidos accidentalmente y define límites de retención.

Exige autenticación fuerte, autorización explícita y, para producción, posiblemente aprobación adicional. Un REPL remoto amplía considerablemente la superficie de ataque.

Probar la detección de completitud

casos_incompletos = [
    "if True:",
    "def funcion(x):",
    "(",
    "'''texto",
]

for fuente in casos_incompletos:
    assert codeop.compile_command(fuente) is None

Añade casos completos, inválidos, Unicode, decorators, comprehensions, match, async y sintaxis específica de la versión soportada.

Compatibilidad entre versiones

codeop utiliza el parser del intérprete en ejecución. Una entrada válida en Python 3.14 puede ser inválida en Python 3.11. Las instrucciones futuras también siguen las features de la versión.

Registra la versión de cada sesión y ejecuta la fuente en el mismo runtime usado para validarla.

Errores frecuentes

  • Tratar None como error de sintaxis.
  • Crear un nuevo CommandCompiler para cada línea.
  • Perder saltos de línea al construir el buffer.
  • Ejecutar código de usuario en el proceso principal.
  • Considerar built-ins reducidos una sandbox.
  • Compartir namespace entre sesiones.
  • No imponer límites de tiempo y recursos.
  • Exponer tracebacks y rutas internas completas.

Buenas prácticas

  • Usa un compilador por sesión.
  • Trata por separado código completo, incompleto e inválido.
  • Conserva líneas y filenames coherentes.
  • Prefiere el módulo code para consolas convencionales.
  • Ejecuta código no confiable en proceso o container aislado.
  • Aplica límites de CPU, memoria, red, filesystem y tiempo.
  • Audita consolas administrativas.
  • Prueba todas las categorías de sintaxis soportadas.

Conclusión

El módulo codeop en Python resuelve la parte sutil de un REPL: determinar si la entrada ya forma código completo y conservar opciones futuras del compilador entre comandos. compile_command() atiende verificaciones puntuales, mientras CommandCompiler mantiene el estado de una sesión.

La compilación es solo una etapa. Ejecutar el objeto resultante sigue siendo tan poderoso como ejecutar un script. Separando análisis y ejecución, aislando sesiones, imponiendo límites de recursos y protegiendo la interfaz, es posible construir consolas, notebooks y herramientas educativas sin convertir la comodidad en acceso irrestricto al servidor.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Desarrollador analizando estructura de código y tablas de símbolos con symtable en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    symtable en Python: ámbitos y símbolos

    Aprende symtable en Python para analizar ámbitos, símbolos, globals, nonlocals, closures, imports, annotations y type parameters.

    Ler mais

    Tempo de leitura: 7 minutos
    03/08/2026
    Monitor con código binario que representa análisis de bytecode con dis en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    dis en Python: entiende el bytecode

    Aprende dis en Python para analizar bytecode, instrucciones, cachés adaptativas, posiciones, tracebacks y detalles internos de CPython.

    Ler mais

    Tempo de leitura: 7 minutos
    03/08/2026
    Pantalla de error que representa diagnóstico de crashes y deadlocks con faulthandler en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    faulthandler en Python: diagnostica bloqueos

    Aprende faulthandler en Python para diagnosticar crashes, deadlocks y timeouts mediante pilas de threads y código nativo.

    Ler mais

    Tempo de leitura: 7 minutos
    03/08/2026
    Portátil con código que representa análisis de traceback y depuración en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    traceback en Python: errores y pila

    Aprende traceback en Python para capturar, formatear y registrar pilas de error sin filtrar datos sensibles ni retener memoria.

    Ler mais

    Tempo de leitura: 6 minutos
    03/08/2026
    Análisis de software que representa introspección de objetos con inspect en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    inspect en Python: introspección de objetos

    Aprende inspect en Python para analizar funciones, clases, firmas, código fuente, decorators, generators, coroutines y frames con seguridad.

    Ler mais

    Tempo de leitura: 6 minutos
    02/08/2026
    Módulo de memoria que representa referencias débiles y cachés en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    weakref en Python: referencias débiles

    Aprende weakref en Python para crear referencias débiles, cachés automáticas, observadores y finalizadores sin retener objetos en memoria.

    Ler mais

    Tempo de leitura: 9 minutos
    28/07/2026