Explorando o Python AST: Como Ler e Modificar Código Programaticamente

O Python oferece uma ferramenta poderosa para análise e manipulação de código fonte: o AST (Abstract Syntax Tree). Com AST, você pode inspecionar, modificar e gerar código Python programaticamente, abrindo portas para aplicações avançadas como:

  • Linters e ferramentas de análise estática

  • Refatoração automatizada de código

  • Geração de código

  • Ferramentas de instrumentação e profiling

Neste post, vamos explorar como usar o módulo ast do Python para ler, interpretar e modificar código, com exemplos práticos, dicas e boas práticas.


1. O que é AST?

AST (Abstract Syntax Tree) é uma representação em árvore de um código fonte, onde cada nó representa uma construção sintática do Python:

  • Funções, classes, loops e condicionais

  • Expressões matemáticas e operações lógicas

  • Chamadas de funções e atribuições

Exemplo simples:

x = 10 + 5

AST correspondente:

Module(
    body=[
        Assign(
            targets=[Name(id='x', ctx=Store())],
            value=BinOp(left=Constant(value=10), op=Add(), right=Constant(value=5))
        )
    ]
)

2. Analisando código com ast

O módulo ast permite converter código em uma árvore de sintaxe:

import ast

code = "x = 10 + 5"
tree = ast.parse(code)
print(ast.dump(tree, indent=4))

Saída:

Module(
    body=[
        Assign(
            targets=[Name(id='x', ctx=Store())],
            value=BinOp(left=Constant(value=10), op=Add(), right=Constant(value=5))
        )
    ],
    type_ignores=[]
)

Explicação:

  • Module: representa o arquivo/código

  • Assign: operação de atribuição

  • BinOp: operação binária (+)

  • Constant: valores literais


3. Percorrendo a AST

Para inspecionar cada nó, use ast.walk ou visitores personalizados.

3.1 Exemplo com ast.walk

for node in ast.walk(tree):
    print(type(node))

Isso imprime o tipo de cada nó (ModuleAssignNameBinOpConstant), útil para análise estática.

3.2 Criando um visitante com NodeVisitor

class Analyzer(ast.NodeVisitor):
    def visit_Assign(self, node):
        print("Encontrada atribuição para:", [t.id for t in node.targets])
        self.generic_visit(node)

analyzer = Analyzer()
analyzer.visit(tree)

Saída:

Encontrada atribuição para: ['x']

Vantagem: você pode criar lógicas específicas para cada tipo de nó.


4. Modificando código com AST

Além de ler, podemos modificar a árvore e gerar código novo.

4.1 Criando um NodeTransformer

class AddTenTransformer(ast.NodeTransformer):
    def visit_BinOp(self, node):
        # Adiciona 10 a qualquer soma
        if isinstance(node.op, ast.Add):
            return ast.BinOp(left=node.left, op=node.op, right=ast.Constant(value=10))
        return node

transformer = AddTenTransformer()
new_tree = transformer.visit(tree)

4.2 Gerando código Python de volta

Python 3.9+ possui ast.unparse:

new_code = ast.unparse(new_tree)
print(new_code)

Saída:

x = 10 + 10

5. Exemplos práticos de uso do AST

5.1 Contando funções em um arquivo Python

class FunctionCounter(ast.NodeVisitor):
    def __init__(self):
        self.count = 0
    def visit_FunctionDef(self, node):
        self.count += 1
        self.generic_visit(node)

with open("script.py") as f:
    tree = ast.parse(f.read())

counter = FunctionCounter()
counter.visit(tree)
print(f"Total de funções: {counter.count}")

5.2 Detectando imports desnecessários

class ImportAnalyzer(ast.NodeVisitor):
    def visit_Import(self, node):
        for alias in node.names:
            print("Import encontrado:", alias.name)
    def visit_ImportFrom(self, node):
        print("From import:", node.module)

tree = ast.parse(open("script.py").read())
ImportAnalyzer().visit(tree)

5.3 Inserindo logging automático em funções

class LoggerInjector(ast.NodeTransformer):
    def visit_FunctionDef(self, node):
        # Adiciona print no início de cada função
        print_stmt = ast.parse(f'print("Entrando na função {node.name}")').body[0]
        node.body.insert(0, print_stmt)
        return node

tree = ast.parse(open("script.py").read())
tree = LoggerInjector().visit(tree)
exec(compile(tree, filename="<ast>", mode="exec"))

Resultado: todas as funções do script imprimirão uma mensagem ao serem chamadas.


6. Boas práticas ao usar AST

  1. Nunca execute código de origem não confiável diretamente com exec.

  2. Mantenha separação entre análise, transformação e execução do código.

  3. Use NodeVisitor para análise e NodeTransformer para alterações.

  4. Teste as transformações para evitar gerar código inválido.

  5. Versionamento de scripts: manter histórico das mudanças AST é útil em refatoração automática.

  6. Performance: AST é ótimo para análise e geração de código, mas não substitui otimizações de runtime.


7. Ferramentas avançadas que usam AST

  • mypy e pyright: análise de tipos estáticos

  • black e autopep8: formatação automática de código

  • pylint e flake8: linters e análise estática

  • Ferramentas de refatoração automática: adicionam logs, instrumentação ou otimizações

Python AST é a base para todas essas ferramentas, permitindo manipulação programática do código fonte.


8. Conclusão

O Python AST é uma ferramenta extremamente poderosa para ler, analisar e modificar código programaticamente. Com AST, você pode:

  • Criar linters e verificações personalizadas

  • Automatizar refatorações

  • Gerar código de forma dinâmica

  • Instrumentar funções para logging ou métricas

  • Desenvolver ferramentas avançadas de análise de código

Próximos passos sugeridos:

  1. Explorar AST em scripts maiores para análise de dependências ou métricas.

  2. Combinar NodeTransformer com geração de código para refatorações automáticas.

  3. Integrar AST com frameworks como astor ou redbaron para manipulação mais amigável.

  4. Criar ferramentas de linting ou análise customizada para projetos específicos.

Comentários

Postagens mais visitadas deste blog

Gerando Relatórios em PDF com Python (ReportLab e FPDF)

Python para Computação Quântica: Introdução com Qiskit

Estrutura Básica de um Programa C# com exemplos