symtable no Python: escopos e símbolos

Publicado em: 03/08/2026
Tempo de leitura: 7 minutos
Desenvolvedor analisando estrutura de código e tabelas de símbolos com symtable no Python

Antes de gerar bytecode, o compilador do Python percorre a árvore sintática e decide o escopo de cada identificador. Ele precisa saber se um nome é local, global, nonlocal, parâmetro, importado, variável livre ou namespace. O módulo symtable no Python expõe essas tabelas de símbolos para análise estática, ferramentas educacionais, linters, geradores de documentação e estudos sobre closures.

Neste guia, você aprenderá a criar uma tabela com symtable(), percorrer funções e classes, consultar símbolos e reconhecer variáveis livres, globals, annotations, comprehensions e type parameters. O conteúdo complementa nossos artigos sobre bytecode com dis, introspecção com inspect, descriptors, singledispatch e tracebacks.

O papel da tabela de símbolos

Uma árvore sintática descreve estruturas como funções, atribuições e chamadas, mas o compilador ainda precisa resolver o significado de cada nome dentro de seu bloco. A tabela de símbolos é criada depois da AST e antes do bytecode.

Ela determina, por exemplo, que um nome atribuído dentro de uma função é local, a menos que exista uma declaração global ou nonlocal. Também identifica variáveis livres usadas por closures.

Criar uma tabela

symtable.symtable() recebe o código, um nome de arquivo para mensagens e o modo de compilação.

import symtable

codigo = """
taxa = 0.1

def total(valor):
    return valor * (1 + taxa)
"""

tabela = symtable.symtable(
    codigo,
    "exemplo.py",
    "exec",
)

print(tabela.get_name())
print(tabela.get_type())

Os modos são semelhantes aos de compile(): exec para módulos e blocos, eval para expressões e single para uma instrução interativa.

Tipos de tabela

get_type() retorna um membro de SymbolTableType. Os tipos principais são módulo, função e classe. Versões modernas também representam escopos de annotations, aliases de tipo, parâmetros de tipo e variáveis de tipo.

from symtable import SymbolTableType

if tabela.get_type() is SymbolTableType.MODULE:
    print("Tabela do módulo")

A documentação oficial de symtable recomenda usar o enum em vez de comparar strings, pois valores textuais podem mudar.

Identificadores do módulo

get_identifiers() retorna os nomes reconhecidos no bloco.

print(list(tabela.get_identifiers()))
# ['taxa', 'total']

get_symbols() devolve objetos Symbol com flags detalhadas.

for simbolo in tabela.get_symbols():
    print(
        simbolo.get_name(),
        simbolo.is_local(),
        simbolo.is_global(),
        simbolo.is_namespace(),
    )

No nível do módulo, nomes atribuídos costumam ser simultaneamente locais ao módulo e globais para o programa.

Consultar um nome específico

lookup() procura um identificador e retorna seu objeto de símbolo.

simbolo = tabela.lookup("total")
print(simbolo.is_assigned())
print(simbolo.is_namespace())

Quando o nome introduz uma função ou classe, is_namespace() será verdadeiro. Um mesmo nome pode ter mais de um namespace se for redefinido em blocos distintos.

Percorrer tabelas filhas

Funções, classes e outros escopos aparecem em get_children().

for filha in tabela.get_children():
    print(
        filha.get_name(),
        filha.get_type(),
        filha.get_lineno(),
    )

has_children() informa se o bloco possui namespaces internos. is_nested() identifica funções ou classes aninhadas.

Analisar uma função

Tabelas de função oferecem métodos específicos para parâmetros, locais, globals, nonlocals e variáveis livres.

funcao = tabela.get_children()[0]

print(funcao.get_parameters())
print(funcao.get_locals())
print(funcao.get_globals())
print(funcao.get_nonlocals())
print(funcao.get_frees())

No exemplo, valor é parâmetro e local. taxa é global porque a função apenas o referencia e não o atribui.

Local versus global implícito

Considere uma leitura de nome sem atribuição local:

codigo = """
configuracao = {}

def obter():
    return configuracao
"""

Na tabela da função, configuracao será global. Isso não garante que o nome exista durante a execução; a tabela apenas classifica o acesso.

Declaração global explícita

is_declared_global() diferencia um nome declarado com global de um global implícito.

codigo = """
contador = 0

def incrementar():
    global contador
    contador += 1
"""

tabela = symtable.symtable(codigo, "contador.py", "exec")
funcao = tabela.get_children()[0]
simbolo = funcao.lookup("contador")

print(simbolo.is_global())
print(simbolo.is_declared_global())

Linters podem usar essa distinção para apontar mutações de estado global.

nonlocal e closures

nonlocal permite alterar uma variável de uma função externa.

codigo = """
def criar_contador():
    valor = 0

    def incrementar():
        nonlocal valor
        valor += 1
        return valor

    return incrementar
"""

A tabela de incrementar classifica valor como nonlocal e livre.

raiz = symtable.symtable(codigo, "closure.py", "exec")
externa = raiz.get_children()[0]
interna = externa.get_children()[0]

print(interna.get_nonlocals())
print(interna.get_frees())

Essa análise explica por que o compilador cria células acessadas por opcodes como LOAD_DEREF.

Flags dos símbolos

Um objeto Symbol possui vários métodos:

  • is_referenced(): o nome é usado no bloco;
  • is_assigned(): existe atribuição;
  • is_parameter(): é parâmetro;
  • is_imported(): foi criado por import;
  • is_local(), is_global() e is_nonlocal();
  • is_free(): referência a escopo externo;
  • is_annotated(): possui annotation;
  • is_namespace(): introduz namespace.

Combine flags em vez de presumir que são mutuamente exclusivas.

Imports

is_imported() identifica nomes criados por instruções de import.

codigo = """
import json
from pathlib import Path
"""

tabela = symtable.symtable(codigo, "imports.py", "exec")

for nome in tabela.get_identifiers():
    simbolo = tabela.lookup(nome)
    print(nome, simbolo.is_imported())

Essa informação pode alimentar analisadores de dependências. Porém, imports dinâmicos com importlib ou __import__() não são representados como uma instrução estática comum.

Annotations

is_annotated() indica que um nome possui annotation.

codigo = """
quantidade: int
preco: float = 10.0
"""

tabela = symtable.symtable(codigo, "tipos.py", "exec")

for nome in tabela.get_identifiers():
    print(nome, tabela.lookup(nome).is_annotated())

O valor e a annotation são aspectos diferentes. Um nome pode estar anotado sem receber valor naquela linha.

Type parameters no Python moderno

O Python 3.12 introduziu sintaxe de parâmetros de tipo e novos escopos internos. O Python 3.14 adicionou is_type_parameter().

codigo = """
def primeiro[T](itens: list[T]) -> T:
    return itens[0]
"""

tabela = symtable.symtable(codigo, "generics.py", "exec")

Esse código exige uma versão que suporte a sintaxe. Ferramentas devem detectar a versão e usar SymbolTableType.TYPE_PARAMETERS em vez de strings.

Classes e métodos

Tabelas de classe herdam de SymbolTable. O método get_methods() lista funções definidas diretamente no corpo, mas foi depreciado no Python 3.14 e está previsto para remoção no 3.16.

Ferramentas novas devem percorrer get_children(), filtrar tabelas de função e considerar escopos de parâmetros de tipo que podem aparecer entre a classe e seus métodos.

Nomes de classe e variáveis livres

O Python 3.14 adicionou is_free_class() para casos em que um nome da classe é livre do ponto de vista de um método.

codigo = """
def externa():
    x = 1
    class C:
        x = 2
        def metodo(self):
            return x
"""

O método retorna o x da função externa, não o atributo do corpo da classe. Essa regra surpreende quem assume que o escopo de classe funciona como um escopo léxico comum.

Comprehensions

O Python 3.14 adicionou is_comp_iter() para variáveis de iteração e is_comp_cell() para células relacionadas a comprehensions inlined.

codigo = """
def quadrados(valores):
    return [valor * valor for valor in valores]
"""

A forma exata da tabela depende das otimizações da versão. Use as flags públicas e evite depender do nome interno de blocos gerados.

is_optimized()

is_optimized() indica se os locais do bloco podem usar armazenamento otimizado. Funções normalmente possuem fast locals, enquanto módulos trabalham com mappings de namespace.

Essa flag ajuda ferramentas que explicam por que locals() e mutações do dicionário local se comportam de formas diferentes em módulos e funções.

Identificador e linha

get_id() retorna um identificador da tabela, e get_lineno() informa a primeira linha do bloco.

for filha in tabela.get_children():
    print(filha.get_id(), filha.get_lineno())

Não persista get_id() como chave estável entre processos ou compilações. Para relatórios, combine arquivo, tipo, nome e linha.

Executar pela linha de comando

Desde Python 3.13, o módulo pode ser executado como script.

python -m symtable programa.py

Sem arquivos, ele lê da entrada padrão. A saída é útil para exploração, mas ferramentas devem consumir a API para obter dados estruturados.

symtable versus ast

A AST oficial descreve a estrutura sintática e permite localizar atribuições, chamadas e expressões. symtable acrescenta a classificação de escopo calculada pelo compilador.

Um linter robusto frequentemente usa ambos: AST para entender o contexto da instrução e tabela de símbolos para saber a qual namespace o nome pertence.

symtable versus inspect

symtable trabalha com código-fonte sem executar o programa. inspect examina objetos vivos depois da importação ou execução. Para análise de código não confiável, a abordagem estática reduz riscos, embora ainda seja necessário proteger o parser contra arquivos enormes.

Exemplo de relatório de nomes

def descrever(tabela, caminho=()):
    atual = caminho + (tabela.get_name(),)

    for simbolo in tabela.get_symbols():
        yield {
            "escopo": ".".join(atual),
            "nome": simbolo.get_name(),
            "local": simbolo.is_local(),
            "global": simbolo.is_global(),
            "nonlocal": simbolo.is_nonlocal(),
            "livre": simbolo.is_free(),
            "parametro": simbolo.is_parameter(),
            "importado": simbolo.is_imported(),
        }

    for filha in tabela.get_children():
        yield from descrever(filha, atual)

Esse relatório pode ser serializado em JSON ou usado em uma interface educacional.

Limitações

O módulo não informa tipos reais, valores de execução, branches alcançados ou imports dinâmicos. Ele também acompanha as regras da versão do compilador em uso. Código com sintaxe de uma versão futura pode gerar SyntaxError.

Para projetos completos, é necessário considerar vários arquivos, pacotes, imports, stubs e configurações de type checking.

Erros frequentes

  • Confundir nome global com existência garantida em runtime.
  • Tratar flags como mutuamente exclusivas.
  • Comparar strings de tipo em vez do enum.
  • Depender de get_methods(), já depreciado.
  • Ignorar escopos de annotations e type parameters.
  • Presumir que classes criam closures como funções.
  • Executar código quando análise estática seria suficiente.
  • Persistir IDs internos como identificadores estáveis.

Boas práticas

  • Use SymbolTableType.
  • Percorra todas as tabelas filhas.
  • Combine symtable com AST.
  • Registre versão e nome do arquivo.
  • Teste globals, nonlocals, closures e comprehensions.
  • Considere escopos de tipagem modernos.
  • Evite APIs depreciadas.
  • Limite tamanho e complexidade de fontes não confiáveis.

Conclusão

O módulo symtable no Python abre uma janela para a fase em que o compilador resolve identificadores e escopos. Ele distingue parâmetros, locais, globals, nonlocals, imports, namespaces e variáveis livres antes da geração do bytecode.

Essa informação é valiosa para linters, ferramentas educacionais, documentação e análise de closures. Ao combinar tabelas de símbolos com AST e considerar as mudanças de tipagem e comprehensions nas versões recentes, você pode compreender o significado dos nomes sem executar o programa nem reproduzir manualmente regras complexas do compilador.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Monitor com código binário representando análise de bytecode com dis no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    dis no Python: entenda o bytecode

    Aprenda dis no Python para desmontar bytecode, analisar instruções, caches adaptativos, posições, tracebacks e detalhes do CPython.

    Ler mais

    Tempo de leitura: 7 minutos
    03/08/2026
    Tela de erro representando diagnóstico de crashes e deadlocks com faulthandler no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    faulthandler no Python: diagnostique travamentos

    Aprenda faulthandler no Python para diagnosticar crashes, deadlocks e timeouts com pilhas de threads e código nativo.

    Ler mais

    Tempo de leitura: 8 minutos
    03/08/2026
    Notebook com código representando análise de traceback e depuração no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    traceback no Python: erros e pilha

    Aprenda traceback no Python para capturar, formatar e registrar pilhas de erro com segurança, sem vazar dados ou reter memória.

    Ler mais

    Tempo de leitura: 6 minutos
    03/08/2026
    Análise de software representando introspecção de objetos com inspect no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    inspect no Python: introspecção de objetos

    Aprenda inspect no Python para analisar funções, classes, assinaturas, código-fonte, decorators, generators e frames com segurança.

    Ler mais

    Tempo de leitura: 8 minutos
    02/08/2026
    Módulo de memória representando referências fracas e caches no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    weakref no Python: referências fracas

    Aprenda weakref no Python para criar referências fracas, caches automáticos, observadores e finalizadores sem reter objetos na memória.

    Ler mais

    Tempo de leitura: 9 minutos
    28/07/2026
    Ícone de arquivo ZIP para artigo sobre zipfile no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    zipfile no Python: arquivos ZIP seguros

    Aprenda a criar, ler, validar e extrair arquivos ZIP com zipfile no Python de forma previsível e segura.

    Ler mais

    Tempo de leitura: 5 minutos
    27/07/2026