symtable en Python: ámbitos y símbolos

Publicado el: 03/08/2026
Tempo de leitura: 7 minutos
Desarrollador analizando estructura de código y tablas de símbolos con symtable en Python

Antes de generar bytecode, el compilador de Python recorre el árbol sintáctico y decide el ámbito de cada identificador. Debe determinar si un nombre es local, global, nonlocal, parámetro, importado, variable libre o namespace. El módulo symtable en Python expone estas tablas de símbolos para análisis estático, herramientas educativas, linters, generadores de documentación y estudios sobre closures.

Esta guía muestra cómo crear una tabla con symtable(), recorrer funciones y clases, consultar símbolos y reconocer variables libres, globals, annotations, comprehensions y type parameters. Complementa nuestros artículos sobre bytecode con dis, introspección con inspect, descriptors, singledispatch y tracebacks.

El papel de una tabla de símbolos

Un árbol de sintaxis abstracta describe funciones, asignaciones y llamadas, pero el compilador todavía debe resolver el significado de cada nombre dentro de su bloque. Las tablas se generan después del AST y antes del bytecode.

Determinan, por ejemplo, que un nombre asignado dentro de una función es local, salvo que una declaración global o nonlocal cambie su clasificación. También identifican variables libres capturadas por closures.

Crear una tabla

symtable.symtable() recibe código fuente, un nombre de archivo utilizado en mensajes y un modo de compilación.

import symtable

codigo = """
tasa = 0.1

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

tabla = symtable.symtable(
    codigo,
    "ejemplo.py",
    "exec",
)

print(tabla.get_name())
print(tabla.get_type())

Los modos siguen a compile(): exec para módulos y bloques, eval para expresiones y single para una instrucción interactiva.

Tipos de tabla

get_type() devuelve un miembro de SymbolTableType. Los tipos centrales representan módulos, funciones y clases. Python moderno también incluye ámbitos de annotations, aliases de tipo, parámetros de tipo y variables de tipo.

from symtable import SymbolTableType

if tabla.get_type() is SymbolTableType.MODULE:
    print("Tabla del módulo")

La documentación oficial de symtable recomienda utilizar el enum en lugar de strings fijas porque los valores textuales pueden cambiar.

Identificadores del módulo

get_identifiers() devuelve los nombres reconocidos en el bloque.

print(list(tabla.get_identifiers()))
# ['tasa', 'total']

get_symbols() devuelve objetos Symbol con flags detalladas.

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

En el nivel del módulo, los nombres asignados suelen ser locales al módulo y globales para el programa.

Consultar un nombre

lookup() recupera la entrada de un identificador.

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

Un nombre introducido por una definición de función o clase es un namespace. El mismo nombre puede estar asociado con varios namespaces si el código lo vuelve a enlazar.

Recorrer tablas hijas

Funciones, clases y otros ámbitos anidados aparecen en get_children().

for hija in tabla.get_children():
    print(
        hija.get_name(),
        hija.get_type(),
        hija.get_lineno(),
    )

has_children() informa si existen namespaces internos, mientras is_nested() identifica una función o clase anidada.

Analizar una función

Las tablas de función ofrecen métodos para parámetros, locales, globals, nonlocals y variables libres.

funcion = tabla.get_children()[0]

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

En el ejemplo, valor es parámetro y local. tasa es global porque la función la lee sin asignarla localmente.

Globals implícitos

Considera una lectura sin asignación local.

codigo = """
configuracion = {}

def obtener():
    return configuracion
"""

En la tabla de la función, configuracion es global. Esto no prueba que el nombre exista durante la ejecución; clasifica cómo se compila la búsqueda.

Declaraciones global explícitas

is_declared_global() diferencia una instrucción global de una búsqueda global implícita.

codigo = """
contador = 0

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

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

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

Un linter puede usar esta diferencia para señalar mutaciones de estado global.

nonlocal y closures

nonlocal permite modificar una variable de una función externa.

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

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

    return incrementar
"""

La tabla interna clasifica valor como nonlocal y libre.

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())

Esto explica por qué el compilador crea celdas accesibles mediante opcodes como LOAD_DEREF.

Flags de símbolos

Un Symbol expone varios predicados:

  • is_referenced(): usado en su bloque;
  • is_assigned(): asignado en el bloque;
  • is_parameter(): parámetro de función;
  • is_imported(): creado por import;
  • is_local(), is_global() e is_nonlocal();
  • is_free(): resuelto en un ámbito externo;
  • is_annotated(): posee annotation;
  • is_namespace(): introduce un namespace.

Combina flags en lugar de asumir que todas son mutuamente excluyentes.

Imports

is_imported() identifica nombres creados por instrucciones import.

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

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

for nombre in tabla.get_identifiers():
    simbolo = tabla.lookup(nombre)
    print(nombre, simbolo.is_imported())

Esto ayuda a analizadores de dependencias. Los imports dinámicos con importlib o __import__() no aparecen como enlaces estáticos normales.

Annotations

is_annotated() informa si un nombre posee annotation.

codigo = """
cantidad: int
precio: float = 10.0
"""

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

for nombre in tabla.get_identifiers():
    print(nombre, tabla.lookup(nombre).is_annotated())

Una annotation y un valor asignado son hechos diferentes. Un nombre puede estar anotado sin recibir un valor en esa línea.

Type parameters en Python moderno

Python 3.12 introdujo sintaxis de parámetros de tipo y ámbitos relacionados. Python 3.14 añade is_type_parameter().

codigo = """
def primero[T](elementos: list[T]) -> T:
    return elementos[0]
"""

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

El parser debe soportar la sintaxis. Las herramientas deberían detectar la versión y usar SymbolTableType.TYPE_PARAMETERS.

Clases y métodos

Las tablas de clase heredan de SymbolTable. get_methods() lista funciones declaradas directamente en el cuerpo, pero fue deprecado en Python 3.14 y está previsto eliminarlo en Python 3.16.

Las herramientas nuevas deberían recorrer get_children(), seleccionar tablas de función y considerar ámbitos de parámetros de tipo que pueden aparecer entre la clase y sus métodos.

Nombres de clase y variables libres

Python 3.14 añade is_free_class() para un nombre de clase que es libre desde la perspectiva de un método.

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

El método devuelve el x de la función externa, no el atributo del cuerpo de la clase. Los bloques de clase no actúan como ámbitos léxicos de función.

Comprehensions

Python 3.14 añade is_comp_iter() para variables de iteración y is_comp_cell() para celdas de comprehensions inlined.

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

La forma exacta de la tabla depende de optimizaciones de la versión. Usa flags públicas en vez de nombres internos de bloques generados.

Almacenamiento optimizado

is_optimized() indica si los locales pueden usar almacenamiento optimizado. Las funciones suelen utilizar fast locals, mientras los módulos usan mappings de namespace.

Esta flag ayuda a explicar diferencias en el comportamiento de locals() dentro de módulos y funciones optimizadas.

IDs y líneas

get_id() devuelve un identificador de tabla y get_lineno() la primera línea del bloque.

for hija in tabla.get_children():
    print(hija.get_id(), hija.get_lineno())

No persistas get_id() como clave estable. Para reportes, combina archivo, tipo, nombre y línea.

Uso desde la terminal

Desde Python 3.13, el módulo puede ejecutarse como script.

python -m symtable programa.py

Sin archivos, lee la entrada estándar. La salida sirve para exploración; las aplicaciones deberían usar la API.

symtable frente a ast

La documentación oficial de AST describe nodos sintácticos y permite localizar asignaciones, llamadas y expresiones. symtable añade la clasificación de ámbito del compilador.

Un linter robusto suele usar ambos: AST para el contexto de la instrucción y la tabla para saber a qué namespace pertenece el nombre.

symtable frente a inspect

symtable trabaja con código fuente sin ejecutarlo. inspect examina objetos vivos después del import o la ejecución. El análisis estático reduce riesgos con código desconocido, aunque todavía deben existir límites para entradas enormes.

Ejemplo de reporte

def describir(tabla, ruta=()):
    actual = ruta + (tabla.get_name(),)

    for simbolo in tabla.get_symbols():
        yield {
            "ambito": ".".join(actual),
            "nombre": simbolo.get_name(),
            "local": simbolo.is_local(),
            "global": simbolo.is_global(),
            "nonlocal": simbolo.is_nonlocal(),
            "libre": simbolo.is_free(),
            "parametro": simbolo.is_parameter(),
            "importado": simbolo.is_imported(),
        }

    for hija in tabla.get_children():
        yield from describir(hija, actual)

El resultado puede serializarse como JSON o mostrarse en una herramienta educativa.

Limitaciones

El módulo no revela tipos ni valores de runtime, branches ejecutados o imports dinámicos. Sigue las reglas del compilador de la versión actual. Código con sintaxis más reciente puede generar SyntaxError.

El análisis de proyectos completos también debe resolver archivos, paquetes, imports, stubs, código generado y configuración del type checker.

Errores frecuentes

  • Asumir que un global existirá durante la ejecución.
  • Tratar todas las flags como mutuamente excluyentes.
  • Comparar strings de tipo en lugar del enum.
  • Depender de get_methods(), ya deprecado.
  • Ignorar ámbitos de annotations y type parameters.
  • Suponer que una clase cierra nombres como una función.
  • Ejecutar código cuando basta un análisis estático.
  • Persistir IDs internos como identificadores estables.

Buenas prácticas

  • Usa SymbolTableType.
  • Recorre todas las tablas hijas.
  • Combina symtable con AST.
  • Registra versión y archivo.
  • Prueba globals, nonlocals, closures y comprehensions.
  • Considera ámbitos modernos de tipado.
  • Evita helpers deprecados.
  • Limita tamaño y complejidad de fuentes no confiables.

Conclusión

El módulo symtable en Python abre una ventana a la fase del compilador que resuelve identificadores y ámbitos. Distingue parámetros, locales, globals, nonlocals, imports, namespaces, annotations y variables libres antes de generar bytecode.

Esta información resulta valiosa para linters, herramientas educativas, documentación y análisis de closures. Combinando tablas de símbolos con AST y considerando nuevos ámbitos de tipado y comprehensions, una herramienta puede comprender el significado de los nombres sin ejecutar el programa ni reimplementar reglas complejas del compilador.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Monitor con código binario que representa análisis de bytecode con dis en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    dis en Python: entiende el bytecode

    Aprende dis en Python para analizar bytecode, instrucciones, cachés adaptativas, posiciones, tracebacks y detalles internos de CPython.

    Ler mais

    Tempo de leitura: 7 minutos
    03/08/2026
    Pantalla de error que representa diagnóstico de crashes y deadlocks con faulthandler en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    faulthandler en Python: diagnostica bloqueos

    Aprende faulthandler en Python para diagnosticar crashes, deadlocks y timeouts mediante pilas de threads y código nativo.

    Ler mais

    Tempo de leitura: 7 minutos
    03/08/2026
    Portátil con código que representa análisis de traceback y depuración en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    traceback en Python: errores y pila

    Aprende traceback en Python para capturar, formatear y registrar pilas de error sin filtrar datos sensibles ni retener memoria.

    Ler mais

    Tempo de leitura: 6 minutos
    03/08/2026
    Análisis de software que representa introspección de objetos con inspect en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    inspect en Python: introspección de objetos

    Aprende inspect en Python para analizar funciones, clases, firmas, código fuente, decorators, generators, coroutines y frames con seguridad.

    Ler mais

    Tempo de leitura: 6 minutos
    02/08/2026
    Módulo de memoria que representa referencias débiles y cachés en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    weakref en Python: referencias débiles

    Aprende weakref en Python para crear referencias débiles, cachés automáticas, observadores y finalizadores sin retener objetos en memoria.

    Ler mais

    Tempo de leitura: 9 minutos
    28/07/2026
    Icono de archivo ZIP para un artículo sobre zipfile en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    zipfile en Python: archivos ZIP seguros

    Aprende a crear, leer, validar y extraer archivos ZIP con zipfile en Python de forma predecible y segura.

    Ler mais

    Tempo de leitura: 5 minutos
    27/07/2026