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.







