Los linters, formateadores, analizadores de seguridad, editores y herramientas educativas necesitan comprender código Python sin ejecutarlo. El módulo tokenize en Python convierte el texto fuente en una secuencia de unidades léxicas como nombres, números, operadores, cadenas, comentarios, saltos de línea e indicadores de indentación. Esta capa se encuentra entre el archivo original y estructuras de nivel superior como el árbol de sintaxis abstracta.
En esta guía aprenderás a leer tokens, respetar la codificación, inspeccionar posiciones exactas, localizar comentarios, reconstruir código, tratar entradas incompletas y combinar tokenize con AST y tablas de símbolos. El contenido complementa nuestros artículos sobre inspect, symtable, dis, expresiones regulares y Ruff.
Qué representa un token
Un token es una unidad léxica reconocida por el analizador. La palabra def, el nombre de una función, los paréntesis, un número, una cadena y un comentario son tokens diferentes. A diferencia de una búsqueda de texto simple, la tokenización comprende cadenas multilínea, caracteres escapados, comentarios, indentación y operadores compuestos.
import tokenize
from io import BytesIO
codigo = b"cantidad = 10 # valor inicial\n"
for elemento in tokenize.tokenize(BytesIO(codigo).readline):
print(elemento)La API basada en bytes es importante porque Python debe determinar la codificación antes de decodificar el archivo.
Campos de TokenInfo
Cada elemento generado es un TokenInfo. Contiene el tipo, el texto exacto, la posición inicial, la posición final y la línea física original.
for elemento in tokenize.tokenize(BytesIO(codigo).readline):
print(elemento.type, elemento.string, elemento.start, elemento.end)Las posiciones se expresan como (línea, columna). Las líneas empiezan en uno y las columnas en cero. Un editor o diagnóstico puede usar estas coordenadas para señalar una región exacta.
Nombres legibles de los tipos
El número del tipo se convierte en un nombre legible mediante token.tok_name.
import token
for elemento in tokenize.tokenize(BytesIO(codigo).readline):
print(token.tok_name[elemento.type], repr(elemento.string))Entre los valores comunes están ENCODING, NAME, OP, NUMBER, STRING, COMMENT, NEWLINE, INDENT, DEDENT y ENDMARKER.
Detectar la codificación
Los archivos Python pueden declarar su codificación en una de las primeras dos líneas. detect_encoding() aplica las mismas reglas que el intérprete.
with open("aplicacion.py", "rb") as archivo:
codificacion, lineas = tokenize.detect_encoding(archivo.readline)
print(codificacion)La documentación oficial de tokenize describe cómo se procesan el BOM UTF-8 y el comentario de codificación. Si ambos se contradicen, se genera un error.
Abrir archivos Python correctamente
tokenize.open() detecta la codificación y devuelve un flujo de texto.
with tokenize.open("aplicacion.py") as archivo:
fuente = archivo.read()Es preferible a imponer UTF-8 cuando una herramienta debe procesar proyectos antiguos o mixtos. En proyectos nuevos, UTF-8 sigue siendo la opción recomendada.
Tokenizar una cadena existente
Cuando el código ya está decodificado, generate_tokens() acepta una función readline que devuelve texto.
from io import StringIO
fuente = "total = precio * cantidad\n"
for elemento in tokenize.generate_tokens(StringIO(fuente).readline):
print(elemento)Esta variante no emite el token ENCODING. Usa la API de bytes para archivos y la API textual cuando la codificación ya fue resuelta.
Encontrar comentarios
Los comentarios aparecen como tokens COMMENT. No es necesario buscar manualmente el carácter #.
comentarios = []
for elemento in tokenize.generate_tokens(StringIO(fuente).readline):
if elemento.type == token.COMMENT:
comentarios.append((elemento.start, elemento.string))Una expresión regular podría confundir un numeral dentro de una cadena con un comentario. El tokenizador distingue esos casos.
Recopilar identificadores
Los tokens NAME pueden formar índices, métricas o ejercicios educativos.
nombres = {
elemento.string
for elemento in tokenize.generate_tokens(StringIO(fuente).readline)
if elemento.type == token.NAME
}El conjunto incluye palabras reservadas. El módulo keyword permite excluirlas.
import keyword
identificadores = {n for n in nombres if not keyword.iskeyword(n)}Esta lista no indica qué ámbito posee cada nombre. Para resolver ámbitos, combina el resultado con AST y symtable.
Tokens de indentación
Python emite INDENT y DEDENT cuando cambia el nivel de un bloque.
fuente = "def doble(valor):\n return valor * 2\n"
for elemento in tokenize.generate_tokens(StringIO(fuente).readline):
if elemento.type in {token.INDENT, token.DEDENT}:
print(token.tok_name[elemento.type], repr(elemento.string))Estos tokens permiten construir visualizaciones de bloques, estadísticas de indentación y diagnósticos más fiables que contar espacios sin comprender las líneas lógicas.
NEWLINE y NL
NEWLINE termina una instrucción lógica. NL representa un salto físico que no termina la instrucción, por ejemplo dentro de paréntesis, corchetes o llaves.
valores = [
1,
2,
]
Los saltos internos generan NL. Los formateadores y herramientas que conservan comentarios deben distinguir ambos tipos.
Tipo exacto de operadores
La mayoría de signos se emiten con el tipo general OP. La propiedad exact_type identifica el operador concreto.
for elemento in tokenize.generate_tokens(StringIO("valor += 1\n").readline):
if elemento.type == token.OP:
print(token.tok_name[elemento.exact_type])Así puedes diferenciar asignación, asignación aumentada, flechas, delimitadores y operadores aritméticos sin mantener una tabla manual.
Reconstruir código
untokenize() vuelve a crear texto o bytes a partir de tokens.
elementos = list(tokenize.tokenize(BytesIO(codigo).readline))
reconstruido = tokenize.untokenize(elementos)
print(reconstruido)La garantía documentada se refiere a la equivalencia de tipos y textos al tokenizar de nuevo. El espaciado exacto puede cambiar.
Una transformación sencilla
Es posible sustituir tokens NAME seleccionados y reconstruir el resultado.
resultado = []
lector = StringIO("valor = valor + 1\n")
for elemento in tokenize.generate_tokens(lector.readline):
if elemento.type == token.NAME and elemento.string == "valor":
elemento = elemento._replace(string="contador")
resultado.append(elemento)
nuevo_codigo = tokenize.untokenize(resultado)Esto es un reemplazo léxico, no una refactorización completa. Puede renombrar variables distintas que comparten el mismo texto en ámbitos diferentes.
Tratar TokenError
Las construcciones incompletas pueden generar TokenError. Algunos ejemplos son una cadena triple sin cerrar o paréntesis abiertos al final del archivo.
try:
list(tokenize.generate_tokens(StringIO("texto = '''abierto").readline))
except tokenize.TokenError as error:
mensaje, posicion = error.args
print(mensaje, posicion)Un buen diagnóstico debe mostrar la posición, conservar el código original y evitar correcciones automáticas sin contexto.
Errores de indentación
Una indentación inválida puede producir IndentationError. Las herramientas deben capturar ambos tipos de fallo.
try:
elementos = list(tokenize.generate_tokens(StringIO(fuente).readline))
except (tokenize.TokenError, IndentationError) as error:
print(f"Código inválido: {error}")Tokenizar no valida toda la sintaxis
Un archivo puede tokenizarse y aun contener una gramática inválida. Usa ast.parse() para validar la sintaxis.
import ast
ast.parse(fuente)Tokenize responde qué unidades léxicas existen. AST explica cómo forman expresiones, instrucciones, funciones, clases y control de flujo.
Combinar tokens y AST
Los nodos AST ofrecen estructura semántica, pero normalmente no conservan todos los comentarios y detalles de formato. Los tokens retienen comentarios y posiciones exactas. Los formateadores, codemods y analizadores suelen utilizar ambas representaciones.
Para determinar el ámbito de los nombres y las closures, añade symtable. Para objetos en ejecución, inspect es apropiado, pero importar código no confiable implica riesgo.
Seguridad y límites
Tokenizar no ejecuta el código, lo que es más seguro que importarlo. Sin embargo, archivos enormes o entradas adversarias pueden consumir memoria y CPU. Limita tamaño, longitud de líneas, anidamiento, cantidad de archivos y tiempo de procesamiento.
Al modificar archivos, escribe primero en una ubicación temporal. Tokeniza y analiza el resultado, ejecuta pruebas y sustituye el original de forma atómica.
Crear un resumen de tokens
from collections import Counter
def resumir(fuente):
conteo = Counter()
lector = StringIO(fuente).readline
for elemento in tokenize.generate_tokens(lector):
conteo[token.tok_name[elemento.type]] += 1
return dict(conteo)El diccionario puede mostrar la cantidad de comentarios, cadenas, operadores, nombres, líneas lógicas y cambios de indentación.
Uso desde la terminal
El módulo incluye una interfaz de línea de comandos:
python -m tokenize aplicacion.pyLa opción -e muestra los nombres exactos de los operadores. Es útil para aprender y depurar transformaciones.
Errores frecuentes
- Buscar comentarios con regex y coincidir con numerales dentro de cadenas.
- Ignorar ENCODING al trabajar con bytes.
- Tratar NL y NEWLINE como equivalentes.
- Usar reemplazo léxico como refactorización consciente de ámbitos.
- Suponer que untokenize conserva todos los espacios.
- Importar código cuando el análisis estático es suficiente.
- Procesar entradas no confiables sin límites.
Buenas prácticas
- Lee archivos como bytes con tokenize.tokenize.
- Usa tokenize.open para obtener texto decodificado.
- Convierte tipos mediante el módulo token.
- Usa exact_type para signos y operadores.
- Combina tokens, AST y symtable según la tarea.
- Valida el resultado antes de sustituir archivos.
- Prueba Unicode, cadenas multilínea, comentarios y entradas incompletas.
Conclusión
El módulo tokenize en Python ofrece una visión precisa del código fuente en el nivel léxico. Identifica nombres, números, cadenas, comentarios, operadores, saltos de línea e indentación, y conserva coordenadas útiles para editores y diagnósticos.
Utilízalo para análisis estático ligero, extracción de comentarios, métricas, enseñanza y transformaciones controladas. Combínalo con AST y symtable para comprender estructura y ámbitos. Con codificación correcta, límites de recursos, tratamiento de errores y validación posterior, puedes crear herramientas fiables sin ejecutar los archivos analizados.







