O módulo ast transforma código-fonte Python em uma árvore sintática abstrata. Em vez de tratar o programa como texto, uma ferramenta passa a trabalhar com nós que representam módulos, funções, classes, chamadas, operadores, nomes, literais e estruturas de controle. Essa camada é usada em linters, formatadores, analisadores de segurança, migrações automáticas, documentação e ferramentas educacionais.
Uma AST descreve a estrutura sintática, não o comportamento completo do programa. Imports dinâmicos, reflexão, monkey patching, descriptors, metaclasses e dados em runtime limitam o que uma análise estática pode concluir. Além disso, compilar ou executar uma árvore modificada executa código com os mesmos riscos de qualquer programa Python.
Analise uma string com parse
ast.parse() recebe código e retorna um nó Module.
import ast
codigo = "resultado = soma(2, 3)"
arvore = ast.parse(codigo, filename="exemplo.py", mode="exec")
print(type(arvore).__name__)
O nome do arquivo é usado em mensagens de erro e tracebacks. Informe um valor real quando a origem for conhecida.
Modos exec, eval e single
mode="exec" analisa um módulo com várias instruções. eval aceita uma expressão. single representa uma instrução interativa.
expressao = ast.parse("1 + 2 * 3", mode="eval")
Escolher o modo incorreto gera SyntaxError ou uma árvore incompatível com a compilação pretendida.
Visualize com dump
ast.dump() produz uma representação textual da árvore.
print(ast.dump(arvore, indent=2, include_attributes=True))
include_attributes=True inclui linhas, colunas e posições finais, úteis para diagnósticos e edição de código.
Estrutura básica
Um módulo contém uma lista body. Uma atribuição é um nó Assign; uma chamada é Call; identificadores são Name; literais modernos costumam ser Constant.
atribuicao = arvore.body[0]
print(type(atribuicao).__name__)
print(type(atribuicao.value).__name__)
Não dependa de índices fixos em ferramentas reais. Percorra e valide os tipos de nós.
Contexto Load, Store e Del
Nós Name, Attribute e Subscript possuem um contexto que indica leitura, escrita ou exclusão.
for no in ast.walk(ast.parse("x = y + 1")):
if isinstance(no, ast.Name):
print(no.id, type(no.ctx).__name__)
Essa distinção é essencial para análise de variáveis definidas e utilizadas.
Percorra com walk
ast.walk() produz o nó e todos os descendentes sem garantir uma ordem adequada para transformação contextual.
chamadas = [
no for no in ast.walk(arvore)
if isinstance(no, ast.Call)
]
Use-o para buscas simples. Para controle de entrada e saída em cada nó, use visitors.
NodeVisitor
Subclasse ast.NodeVisitor e defina métodos visit_TipoDoNo.
class ColetorFuncoes(ast.NodeVisitor):
def __init__(self):
self.nomes = []
def visit_FunctionDef(self, no):
self.nomes.append(no.name)
self.generic_visit(no)
coletor = ColetorFuncoes()
coletor.visit(ast.parse(codigo_fonte))
print(coletor.nomes)
Chame generic_visit() quando também quiser percorrer filhos. Se omitir, a visita daquele ramo termina.
Funções assíncronas
AsyncFunctionDef é diferente de FunctionDef. Uma ferramenta que coleta funções deve tratar ambos.
def visit_AsyncFunctionDef(self, no):
self.nomes.append(no.name)
self.generic_visit(no)
O mesmo vale para comprehensions assíncronas e AsyncFor/AsyncWith.
Classes e escopos
ClassDef, funções, lambdas e comprehensions criam contextos de escopo distintos. Apenas percorrer nomes não resolve binding lexical.
Para uma análise de símbolos, combine AST com symtable e uma pilha explícita de escopos.
Posições no código
Muitos nós possuem lineno, col_offset, end_lineno e end_col_offset.
for no in ast.walk(arvore):
if isinstance(no, ast.Call):
print(no.lineno, no.col_offset, no.end_lineno, no.end_col_offset)
Offsets de coluna são relacionados à representação usada pelo parser e precisam de cuidado com Unicode ao mapear para interfaces.
Recupere o trecho original
ast.get_source_segment(codigo, no) retorna o texto correspondente quando as posições estão disponíveis.
trecho = ast.get_source_segment(codigo, chamadas[0])
O resultado preserva o trecho, mas a AST não guarda comentários e toda a formatação de maneira suficiente para edição perfeita.
Comentários e tokens
Comentários comuns não aparecem como nós da AST. Ferramentas que precisam preservar comentários, whitespace e estilo devem usar tokens ou uma concrete syntax tree.
A AST é excelente para significado estrutural, mas não para round-trip idêntico do arquivo.
Literal seguro com literal_eval
ast.literal_eval() avalia apenas estruturas literais suportadas, como strings, bytes, números, tuplas, listas, dicionários, sets, booleanos e None.
configuracao = ast.literal_eval("{'tentativas': 3, 'ativo': True}")
É mais restrito que eval(), mas não deve receber entradas gigantes ou profundamente aninhadas. Dados maliciosos podem consumir memória, CPU ou profundidade de pilha.
Não use eval em código não confiável
ast.parse() não executa o código, porém compile() e exec() executarão a árvore. Validar alguns nós não cria automaticamente um sandbox seguro.
Python possui muitas formas indiretas de acessar objetos e executar operações. Para entrada hostil, use isolamento de processo, limites e uma linguagem realmente restrita.
Transforme com NodeTransformer
NodeTransformer permite substituir nós retornando um novo objeto.
class TrocarNome(ast.NodeTransformer):
def visit_Name(self, no):
if no.id == "antigo":
return ast.copy_location(
ast.Name(id="novo", ctx=no.ctx),
no,
)
return no
arvore = TrocarNome().visit(arvore)
ast.fix_missing_locations(arvore)
Preserve o contexto e copie posições para melhorar erros e debug.
Remova ou expanda nós
Um transformer pode retornar None para remover um nó de uma lista de instruções ou uma lista de nós para substituir uma instrução por várias. Nem todo campo aceita essas formas.
Valide a árvore com compilação e testes.
fix_missing_locations
Nós criados manualmente podem não possuir informações de linha. fix_missing_locations() preenche valores a partir dos pais.
Isso permite compilar, mas não produz automaticamente posições perfeitas para ferramentas editoriais.
copy_location e increment_lineno
copy_location(novo, antigo) copia localização. increment_lineno() desloca linhas de uma árvore, útil ao inserir prefixos.
Tracebacks úteis dependem de filename e posições coerentes.
Converta de volta com unparse
ast.unparse() gera código Python equivalente a partir de uma árvore.
codigo_novo = ast.unparse(arvore)
O resultado pode mudar aspas, parênteses e formatação. Ele busca equivalência sintática, não preservação textual.
Compile a árvore
compile() aceita uma AST depois que os campos obrigatórios estão corretos.
objeto = compile(arvore, "transformado.py", "exec")
namespace = {}
exec(objeto, namespace)
Execute apenas código confiável e controle o namespace. Um dicionário de globals reduz exposição acidental, mas não cria sandbox.
Versão da gramática
A estrutura aceita pelo parser muda com a linguagem. Ferramentas que suportam vários Pythons precisam testar cada versão e evitar assumir que um tipo de nó existe em todas.
feature_version pode solicitar uma aproximação de gramática anterior dentro dos limites do interpretador atual, mas não substitui testar na versão de destino.
Type comments
Algumas análises podem solicitar comentários de tipo durante o parse. Ainda assim, type hints modernos aparecem principalmente em nós de annotation.
Para semântica de tipos completa, use um type checker ou sua API, não apenas AST.
Decorators
Funções e classes possuem decorator_list. Decorators são expressões executadas e podem alterar radicalmente o objeto criado.
Uma análise estática deve relatar incerteza em vez de presumir que uma função decorada mantém comportamento original.
Chamadas perigosas
Um linter pode procurar padrões como eval, exec, subprocess com shell ou abertura insegura. Porém comparar apenas o texto do nome produz falsos positivos e negativos.
Aliases, imports e reassignment exigem resolução de símbolos e, mesmo assim, análise estática não garante segurança.
Imports
Import e ImportFrom expõem módulos e aliases declarados.
for no in ast.walk(arvore):
if isinstance(no, ast.ImportFrom):
print(no.module, [alias.name for alias in no.names])
Imports dinâmicos e condicionais precisam de tratamento separado.
Complexidade e métricas
Ferramentas podem contar branches, loops, handlers e comprehensions para estimar complexidade. Métricas são sinais para revisão, não prova de qualidade.
Documente o algoritmo e mantenha resultados estáveis entre versões.
Análise de vários arquivos
Para um projeto, descubra arquivos, leia com encoding correto, analise individualmente e agregue resultados. Erros de um arquivo não devem apagar diagnósticos dos demais.
O processamento pode ser paralelizado com limites; veja concurrent.futures no Python. Evite enviar árvores enormes entre processos quando basta retornar diagnósticos compactos.
Encoding de arquivos
Use tokenize.open() para respeitar declaração de encoding do arquivo Python. Ler tudo como UTF-8 pode falhar em código legado.
Erros de sintaxe
ast.parse() gera SyntaxError. Registre filename, linha, offset e mensagem, mas não pare todo o lote.
try:
arvore = ast.parse(codigo, filename=caminho)
except SyntaxError as erro:
relatar(caminho, erro.lineno, erro.offset, erro.msg)
Recursão e entradas grandes
Árvores muito profundas podem atingir limites de recursão em visitors e transformações. Arquivos gigantes também consomem memória.
Defina limites operacionais, trate exceções e não aumente o limite de recursão sem compreender o risco.
Testes de transformações
Compare comportamento antes e depois, compile a árvore, execute testes e inspecione o código gerado. Inclua comprehensions, async, pattern matching, decorators, f-strings e annotations.
Use casos negativos para garantir que a transformação não altera nomes em escopos errados.
Erros comuns
Os erros mais frequentes são esquecer generic_visit(), ignorar AsyncFunctionDef, perder contexto Load/Store, criar nós sem localização, esperar preservar comentários, usar literal_eval() sem limites, executar árvore não confiável e presumir que a AST resolve comportamento dinâmico.
Conclusão
ast transforma código Python em uma estrutura navegável e modificável. Use visitors para análise, transformers para mudanças, posições para diagnósticos e unparse() quando equivalência for suficiente.
Mantenha limites de segurança, teste por versão e trate resultados como análise estática, não verdade absoluta. Consulte a documentação oficial de ast e a documentação de symtable.







