O módulo symtable expõe as tabelas de símbolos produzidas pelo compilador do Python antes da geração de bytecode. Ele informa quais nomes pertencem a cada escopo, quais são parâmetros, imports, variáveis locais, globals, nonlocals, free variables, referências e namespaces internos. Essa camada é útil em linters, refactorings, ferramentas educacionais, análise estática e inspeção de closures.
Uma symbol table não executa o programa e não resolve todos os comportamentos dinâmicos. Atribuições via globals(), setattr(), imports dinâmicos, decorators, metaclasses e monkey patching continuam fora de uma análise completa. Ainda assim, ela representa as decisões reais de escopo tomadas pelo compilador e é mais confiável que inferir bindings apenas a partir de tokens.
Crie uma tabela de símbolos
A função symtable.symtable() recebe o fonte, um filename e o modo de compilação.
import symtable
codigo = """
x = 10
def somar(y):
z = x + y
return z
"""
tabela = symtable.symtable(codigo, "exemplo.py", "exec")
print(tabela.get_type())
O modo pode ser exec, eval ou single, como em compile().
Erros de sintaxe
A construção da tabela usa o compilador e pode gerar SyntaxError.
try:
tabela = symtable.symtable(codigo, caminho, "exec")
except SyntaxError as erro:
relatar(caminho, erro.lineno, erro.offset, erro.msg)
Em análise de vários arquivos, registre o erro e continue com os demais.
SymbolTable principal
A tabela raiz representa o módulo ou expressão.
print(tabela.get_name())
print(tabela.get_type())
print(tabela.get_lineno())
O nome e a linha ajudam a criar diagnósticos, mas o significado exato depende do tipo de tabela.
Identificadores
get_identifiers() devolve nomes conhecidos naquele escopo.
for nome in sorted(tabela.get_identifiers()):
print(nome)
A coleção inclui bindings e referências relevantes ao compilador, não apenas atribuições textuais.
lookup
lookup(name) devolve um objeto Symbol com propriedades do nome.
simbolo = tabela.lookup("x")
print(simbolo.is_global())
print(simbolo.is_assigned())
print(simbolo.is_referenced())
Consultar um nome inexistente pode gerar erro; verifique os identificadores ou trate a exceção.
Variáveis locais
is_local() indica que o nome é local ao escopo analisado.
funcao = tabela.lookup("somar").get_namespace()
for nome in funcao.get_identifiers():
simbolo = funcao.lookup(nome)
if simbolo.is_local():
print("local", nome)
Parâmetros também são locals, mas possuem a marca específica is_parameter().
Parâmetros
is_parameter() identifica argumentos de função.
for nome in funcao.get_parameters():
print(nome)
Ferramentas podem usar essa informação para detectar argumentos não utilizados ou shadowing.
Globals implícitos e explícitos
Um nome pode ser considerado global porque não existe binding local ou porque foi declarado com global.
contador = 0
def incrementar():
global contador
contador += 1
Use is_global() e, quando disponível, is_declared_global() para diferenciar casos relevantes.
Nonlocal
nonlocal liga um nome a um escopo de função externo, mas não ao módulo.
def externa():
total = 0
def interna():
nonlocal total
total += 1
return total
return interna
O símbolo interno pode ser identificado por is_nonlocal().
Free variables
Uma free variable é usada por uma função interna e fornecida por um escopo externo.
interna = funcao_externa.get_children()[0]
print(interna.get_frees())
Esses nomes participam da criação de closures.
Cell variables
Quando uma variável local de uma função é capturada por uma função interna, o compilador precisa armazená-la em uma cell.
A API da symbol table permite inferir essa relação comparando locals do escopo externo com frees dos filhos. Code objects também expõem co_cellvars e co_freevars.
Imports
is_imported() informa que um nome foi introduzido por import.
import json as serializador
from pathlib import Path
O alias, e não necessariamente o nome original do módulo, aparece como binding.
Atribuições
is_assigned() identifica nomes que recebem um binding naquele escopo.
Atribuição inclui formas além de =, como targets de loops, imports, definições e determinados patterns. Confirme com AST quando precisar classificar a origem.
Referências
is_referenced() indica uso do nome em uma expressão ou operação relevante.
Uma variável atribuída mas nunca referenciada pode ser candidata a aviso, mas considere APIs públicas, efeitos de decorators e convenções de nomes.
Namespaces
Um símbolo de função ou classe pode possuir uma tabela filha.
simbolo = tabela.lookup("somar")
if simbolo.is_namespace():
namespace = simbolo.get_namespace()
get_namespaces() é necessário quando o mesmo nome produz mais de um namespace em situações específicas.
get_children
get_children() devolve tabelas aninhadas.
def mostrar(tabela, nivel=0):
print(" " * nivel, tabela.get_type(), tabela.get_name())
for filha in tabela.get_children():
mostrar(filha, nivel + 1)
Isso permite visualizar a árvore de escopos.
Funções
Tabelas de função oferecem helpers como get_parameters(), get_locals(), get_globals(), get_nonlocals() e get_frees(), conforme a versão.
Use feature detection quando a ferramenta suporta várias versões do Python.
Classes
Classes possuem regras de escopo diferentes de funções. O corpo é executado em um namespace próprio, mas métodos não capturam automaticamente variáveis da classe como closure.
class Exemplo:
valor = 10
def metodo(self):
return valor
Nesse caso, valor dentro do método não significa automaticamente Exemplo.valor.
__class__ e métodos
O compilador pode criar uma referência especial a __class__ para recursos como super() sem argumentos.
Ferramentas devem aceitar símbolos gerados ou implícitos pelo compilador.
Lambdas
Uma lambda cria um novo escopo de função e aparece como tabela filha.
dobrar = lambda x: x * 2
O nome da tabela pode refletir a forma interna usada pelo compilador.
Comprehensions
Comprehensions possuem escopos próprios em Python moderno. A variável de iteração não vaza para o escopo externo.
quadrados = [x * x for x in valores]
A symbol table pode expor uma tabela interna associada à comprehension.
Generators
Generator expressions também criam escopos internos e podem capturar nomes externos.
Use a árvore de tabelas para localizar frees e parâmetros implícitos.
Async functions
Funções async seguem regras de escopo semelhantes às funções normais, com comportamento de execução diferente.
A tabela de símbolos não informa se uma chamada foi aguardada corretamente; isso exige AST e análise de fluxo.
Type annotations
Annotations podem criar referências e bindings conforme a sintaxe e a versão. Avaliação adiada ou mecanismos modernos de annotations afetam o comportamento.
Não conclua o runtime de uma annotation apenas pela presença de um nome na tabela.
Type aliases e novos escopos
Versões recentes da linguagem introduzem construções que podem criar tipos adicionais de symbol table. Ferramentas devem usar os enums e APIs da versão em execução em vez de comparar strings fixas.
Inclua casos de type parameters e aliases na suíte quando suportados.
Pattern matching
Patterns podem introduzir bindings locais.
match valor:
case {"id": identificador}:
usar(identificador)
A symbol table reconhece o binding, enquanto a AST mostra sua origem no pattern.
Exception targets
O nome em except Exception as erro recebe um binding local com regras de limpeza específicas.
Uma análise de “definido depois” precisa de controle de fluxo, não apenas da tabela.
del
del nome afeta o binding, mas a symbol table não é uma simulação temporal de valores.
Para detectar uso antes de definição ou após delete, construa um grafo de controle.
UnboundLocalError
Uma atribuição em qualquer ponto da função pode fazer o compilador classificar o nome como local, mesmo quando a leitura ocorre antes.
x = 10
def exemplo():
print(x)
x = 20
A symbol table ajuda a explicar por que x é local e a execução gera UnboundLocalError.
Shadowing
Uma variável local pode ocultar um import, builtin ou nome externo.
Nem todo shadowing é erro. Linters devem considerar convenções, duração do escopo e clareza.
Builtins
Um nome que não é local ou global explícito pode ser resolvido em builtins em runtime.
A symbol table não garante que o builtin original será usado, porque __builtins__ e globals podem ser alterados.
Renomeação segura
Para renomear uma variável, combine tokens para posições, AST para contexto e symtable para binding.
Não substitua toda ocorrência textual do nome. Atributos, chaves de dicionário, strings e símbolos de outro escopo são diferentes.
Detecte parâmetros não usados
Compare parâmetros com símbolos referenciados no mesmo escopo.
Ignore nomes convencionais como _ e parâmetros exigidos por interfaces, callbacks ou overrides.
Imports não usados
Um símbolo importado e não referenciado pode indicar cleanup, mas imports também executam side effects ou registram plugins.
Uma ferramenta deve permitir exceções e compreender __all__.
Globals mutáveis
A tabela mostra acesso global, mas não informa thread safety ou mutabilidade.
Use análise adicional para detectar atribuições e operações sobre objetos compartilhados.
Integração com tokenize
tokenize fornece comentários, grafia e posições. Symtable fornece binding e escopo.
Veja tokenize no Python.
Integração com AST
A AST mostra onde o nome aparece e qual construção o contém. A symbol table informa como o compilador o classifica.
Veja ast no Python.
Integração com dis
Depois da tabela de símbolos, o compilador seleciona instruções de load e store apropriadas. Comparar com dis mostra o resultado.
Veja dis no Python.
Análise entre arquivos
Cada arquivo possui uma tabela de módulo independente. Resolver imports e reexports exige um índice do projeto.
Não execute imports durante análise estática; isso pode causar side effects.
Arquivos com encoding
Leia fonte com tokenize.open() antes de chamar symtable().
Preserve filename real para mensagens de erro.
Entrada grande
Limite bytes, linhas, profundidade e tempo ao analisar uploads ou repositórios não confiáveis.
Execute o compilador em um processo isolado quando o serviço for público.
Versões do Python
Tipos de tabela, métodos e classificação podem evoluir com novas construções da linguagem.
Teste a ferramenta em cada versão suportada e use feature detection.
Não é type checker
Symtable não resolve tipos, overloads, protocolos ou inferência. Ele descreve bindings léxicos.
Para análise de tipos, integre um type checker dedicado.
Não é controle de fluxo
A tabela não informa ordem de execução, branches alcançáveis ou valores definidos em todos os caminhos.
Construa CFG ou use ferramentas existentes para regras temporais.
Segurança
Analisar uma symbol table não torna seguro executar o código. Decorators, imports e runtime podem executar operações arbitrárias.
Não use uma allowlist de símbolos como sandbox.
Testes
Inclua módulos, funções, nested functions, closures, classes, lambdas, comprehensions, generators, async, global, nonlocal, annotations, match e syntax errors.
Compare resultados com execução apenas em fixtures confiáveis.
Erros comuns
Os erros mais frequentes são inferir binding apenas por texto, confundir global implícito e declarado, ignorar scopes de comprehensions, tratar classe como função, fazer rename sem namespace, usar symtable como type checker, executar imports para resolver nomes e depender de APIs de uma única versão.
Conclusão
symtable revela como o compilador organiza nomes e escopos antes do bytecode. Use lookup() e propriedades de Symbol para identificar locals, globals, imports, parâmetros, nonlocals e free variables, e percorra get_children() para construir a árvore de namespaces.
Combine a tabela com tokens, AST e análise de fluxo quando precisar de refactoring ou linting preciso. Consulte a documentação oficial de symtable.







