ast en Python: analiza código fuente

Publicado el: 27/08/2026
Tempo de leitura: 6 minutos
Close-up of a computer screen displaying colorful programming code with depth of field.

El módulo ast convierte código fuente Python en un árbol de sintaxis abstracta. En lugar de tratar un programa como texto plano, una herramienta trabaja con nodos que representan módulos, funciones, clases, llamadas, operadores, nombres, literales y estructuras de control. Linters, formatters, analizadores de seguridad, migraciones automáticas, documentación y herramientas educativas utilizan esta capa estructural.

Una AST describe la sintaxis, no todo el comportamiento en runtime. Imports dinámicos, reflexión, monkey patching, descriptors, metaclasses y datos dependientes de ejecución limitan lo que un análisis estático puede demostrar. Compilar o ejecutar un árbol modificado también ejecuta código con los mismos riesgos que cualquier programa Python.

Analiza texto con parse

ast.parse() acepta código y devuelve un nodo Module.

import ast

codigo = "resultado = sumar(2, 3)"
arbol = ast.parse(codigo, filename="ejemplo.py", mode="exec")
print(type(arbol).__name__)

El filename aparece en errores y tracebacks. Proporciona la ruta real cuando sea conocida.

Modos exec, eval y single

mode="exec" analiza un módulo con instrucciones. eval acepta una expresión y single representa entrada interactiva.

expresion = ast.parse("1 + 2 * 3", mode="eval")

Elegir el modo incorrecto produce SyntaxError o un árbol incompatible con la compilación deseada.

Inspecciona con dump

ast.dump() crea una representación legible.

print(ast.dump(arbol, indent=2, include_attributes=True))

include_attributes=True incluye líneas y columnas, útiles para diagnósticos y edición.

Estructura básica de nodos

Un módulo contiene una lista body. Una asignación es Assign, una llamada es Call, los identificadores son Name y los literales comunes suelen ser Constant.

asignacion = arbol.body[0]
print(type(asignacion).__name__)
print(type(asignacion.value).__name__)

Las herramientas reales no deberían depender de índices fijos. Recorre y valida tipos.

Contexto Load, Store y Del

Los nodos Name, Attribute y Subscript contienen un contexto que indica lectura, escritura o eliminación.

for nodo in ast.walk(ast.parse("x = y + 1")):
    if isinstance(nodo, ast.Name):
        print(nodo.id, type(nodo.ctx).__name__)

La diferencia es esencial para rastrear definiciones y usos.

Recorre con walk

ast.walk() produce un nodo y sus descendientes sin ofrecer hooks contextuales de entrada y salida.

llamadas = [
    nodo for nodo in ast.walk(arbol)
    if isinstance(nodo, ast.Call)
]

Es cómodo para búsquedas simples. Usa visitors cuando el orden y el scope importen.

NodeVisitor

Subclasifica ast.NodeVisitor y define métodos visit_TipoDeNodo.

class ColectorFunciones(ast.NodeVisitor):
    def __init__(self):
        self.nombres = []

    def visit_FunctionDef(self, nodo):
        self.nombres.append(nodo.name)
        self.generic_visit(nodo)

colector = ColectorFunciones()
colector.visit(ast.parse(codigo_fuente))
print(colector.nombres)

Llama a generic_visit() cuando también quieras visitar hijos. Omitirlo detiene el recorrido en ese ramo.

Funciones async

AsyncFunctionDef es distinto de FunctionDef. Un colector debe manejar ambos.

def visit_AsyncFunctionDef(self, nodo):
    self.nombres.append(nodo.name)
    self.generic_visit(nodo)

La misma atención aplica a comprehensions async, AsyncFor y AsyncWith.

Clases y scopes

ClassDef, funciones, lambdas y comprehensions crean reglas de scope distintas. Recopilar nombres no resuelve binding léxico.

Para análisis de símbolos, combina AST con symtable y mantiene una pila explícita de scopes.

Posiciones en el fuente

Muchos nodos incluyen lineno, col_offset, end_lineno y end_col_offset.

for nodo in ast.walk(arbol):
    if isinstance(nodo, ast.Call):
        print(nodo.lineno, nodo.col_offset, nodo.end_lineno, nodo.end_col_offset)

Los offsets de columna requieren cuidado al mapear texto Unicode a una interfaz.

Recupera el segmento original

ast.get_source_segment(codigo, nodo) devuelve el texto correspondiente cuando existen posiciones.

segmento = ast.get_source_segment(codigo, llamadas[0])

La AST no conserva todos los comentarios y decisiones de formato necesarias para un round trip perfecto.

Comentarios y tokens

Los comentarios normales no aparecen como nodos AST. Herramientas que necesiten preservar comentarios, whitespace, comillas y estilo deben usar tokens o una concrete syntax tree.

La AST está diseñada para significado estructural, no preservación byte a byte.

Literales con literal_eval

ast.literal_eval() acepta estructuras literales soportadas: strings, bytes, números, tuplas, listas, diccionarios, sets, booleanos y None.

configuracion = ast.literal_eval("{'intentos': 3, 'activo': True}")

Es mucho más restringido que eval(), pero no debería recibir entradas gigantes o profundamente anidadas. Pueden consumir memoria, CPU o stack.

No uses eval con código no confiable

ast.parse() no ejecuta código, pero compile(), exec() y eval() sí lo harán. Rechazar algunos nodos no crea automáticamente un sandbox seguro.

Para entrada hostil, usa aislamiento de proceso, límites y un lenguaje realmente restringido.

Transforma con NodeTransformer

NodeTransformer permite reemplazar un nodo devolviendo otro.

class Renombrar(ast.NodeTransformer):
    def visit_Name(self, nodo):
        if nodo.id == "antiguo":
            return ast.copy_location(
                ast.Name(id="nuevo", ctx=nodo.ctx),
                nodo,
            )
        return nodo

arbol = Renombrar().visit(arbol)
ast.fix_missing_locations(arbol)

Conserva el contexto y copia posiciones para mejores errores.

Elimina o expande instrucciones

Un transformer puede devolver None para eliminar un nodo de una lista de instrucciones o una lista para sustituir una instrucción por varias. No todos los campos aceptan esas formas.

Compila y prueba cada árbol transformado.

fix_missing_locations

Nodos creados manualmente pueden no tener información de línea. fix_missing_locations() completa valores faltantes desde los padres.

Permite compilar, pero no crea automáticamente posiciones editoriales perfectas.

copy_location e increment_lineno

copy_location(nuevo, antiguo) copia ubicación. increment_lineno() desplaza líneas, útil al insertar un prefijo generado.

Tracebacks útiles dependen de filename y posiciones coherentes.

Genera código con unparse

ast.unparse() produce código Python desde una AST.

codigo_nuevo = ast.unparse(arbol)

El resultado puede cambiar comillas, paréntesis, whitespace y layout. Busca equivalencia sintáctica, no conservación textual.

Compila una AST

compile() acepta un árbol válido con campos y ubicaciones requeridos.

objeto = compile(arbol, "transformado.py", "exec")
namespace = {}
exec(objeto, namespace)

Ejecuta solo código confiable. Un globals limitado reduce exposición accidental, pero no crea sandbox.

Versiones de gramática

La forma del árbol cambia al evolucionar Python. Herramientas que soportan varias versiones deben probar cada target y no asumir que todos los nodos existen siempre.

feature_version puede solicitar una aproximación a una gramática anterior, pero no sustituye ejecutar tests en la versión objetivo.

Type comments y annotations

El parse puede conservar ciertos comentarios de tipo, mientras las annotations modernas aparecen en nodos de argumentos, asignaciones y definiciones.

Para semántica de tipos completa, usa un type checker o su API.

Decorators

Funciones y clases poseen decorator_list. Los decorators son expresiones ejecutadas y pueden sustituir o modificar radicalmente el objeto.

Un analizador estático debe comunicar incertidumbre en lugar de asumir comportamiento original.

Busca llamadas peligrosas

Un linter puede buscar eval, exec, subprocesses con shell o deserialización insegura. Comparar únicamente nombres textuales produce falsos positivos y negativos.

Aliases, imports, shadowing y reassignment requieren resolución de símbolos, y aun así el análisis estático no garantiza seguridad.

Imports

Import e ImportFrom exponen módulos y aliases declarados.

for nodo in ast.walk(arbol):
    if isinstance(nodo, ast.ImportFrom):
        print(nodo.module, [alias.name for alias in nodo.names])

Imports dinámicos y condicionales requieren tratamiento separado.

Métricas de complejidad

Las herramientas pueden contar branches, loops, handlers, comprehensions y operaciones booleanas para estimar complejidad. Son señales para revisión, no prueba de calidad.

Documenta el algoritmo y mantén resultados estables.

Analiza muchos archivos

Descubre archivos, léelos con encoding correcto, analiza cada uno independientemente y agrega diagnósticos. Un error de sintaxis no debe borrar resultados del resto.

El análisis batch puede usar concurrencia limitada; consulta concurrent.futures en Python. Evita transferir árboles enormes entre procesos si bastan diagnósticos compactos.

Encoding de fuente Python

Usa tokenize.open() al leer archivos Python para respetar la declaración de encoding. Asumir UTF-8 puede fallar en proyectos legacy.

Errores de sintaxis

ast.parse() lanza SyntaxError. Registra filename, línea, offset y mensaje sin abortar todo el lote.

try:
    arbol = ast.parse(codigo, filename=ruta)
except SyntaxError as error:
    reportar(ruta, error.lineno, error.offset, error.msg)

Recursión y entrada enorme

Árboles muy profundos pueden alcanzar límites de recursión en visitors y transformaciones. Archivos gigantes también consumen memoria.

Define límites, maneja fallos y no aumentes el límite de recursión sin comprender el riesgo del stack nativo.

Prueba transformaciones

Compara comportamiento antes y después, compila el árbol, ejecuta tests e inspecciona código generado. Incluye comprehensions, async, pattern matching, decorators, f-strings y annotations.

Usa casos negativos para comprobar que un rename no afecta el scope equivocado.

Errores comunes

Los fallos frecuentes son olvidar generic_visit(), ignorar AsyncFunctionDef, perder contexto Load/Store, crear nodos sin posición, esperar conservar comentarios, usar literal_eval() sin límites, ejecutar árboles no confiables y asumir que la AST resuelve comportamiento dinámico.

Conclusión

ast convierte código Python en una estructura navegable y transformable. Usa visitors para análisis, transformers para cambios, ubicaciones para diagnósticos y unparse() cuando la equivalencia sea suficiente.

Aplica límites, prueba cada versión soportada y trata los resultados como análisis estático, no verdad absoluta. Consulta la documentación oficial de ast y la documentación de symtable.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Close-up of electric plug and socket with vibrant lighting, showcasing technology and energy concepts.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    socket en Python: redes TCP y UDP

    Aprende socket en Python para clientes y servidores TCP y UDP, framing, timeouts, IPv6, concurrencia, TLS y seguridad de red.

    Ler mais

    Tempo de leitura: 6 minutos
    27/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

    multiprocessing en Python: varios núcleos

    Aprende multiprocessing en Python con procesos, pools, queues, pipes, memoria compartida, cancelación, seguridad y shutdown correcto.

    Ler mais

    Tempo de leitura: 7 minutos
    26/08/2026
    Bright yellow and blue shopping carts arranged in orderly rows outdoors.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    concurrent.futures: hilos y procesos en paralelo

    Aprende concurrent.futures en Python con threads, procesos, Future, timeouts, cancelación, backpressure y prevención de deadlocks.

    Ler mais

    Tempo de leitura: 7 minutos
    26/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    winsound en Python: audio en Windows

    Aprende winsound en Python para reproducir WAV, sonidos del sistema, beeps, loops y notificaciones asíncronas de forma segura en Windows.

    Ler mais

    Tempo de leitura: 6 minutos
    26/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    winreg en Python: Registro de Windows

    Aprende winreg en Python para leer y escribir el Registro de Windows, gestionar tipos, permisos, vistas WOW64, eliminaciones y seguridad.

    Ler mais

    Tempo de leitura: 7 minutos
    26/08/2026
    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