Los formateadores, colorizadores, analizadores de estilo y herramientas de refactorización necesitan dividir el código Python en unidades como nombres, números, strings, operadores, comentarios e indentación. El módulo tokenize en Python proporciona un scanner léxico de la biblioteca estándar que conserva comentarios y posiciones, permitiendo inspeccionar o reescribir código fuente sin ejecutarlo.
Esta guía explica tokenize(), generate_tokens(), untokenize(), detect_encoding() y tokenize.open(). Complementa nuestros artículos sobre bytecode con dis, tablas de símbolos, entradas interactivas con codeop, inspect y comparación de textos.
Qué significa tokenizar
La tokenización agrupa caracteres en unidades léxicas. En total = precio * 2, por ejemplo, existen nombres, un operador de asignación, un operador de multiplicación, un número y un final de línea lógico.
El tokenizer no construye un árbol sintáctico completo ni resuelve ámbitos. Conserva detalles útiles para presentación y transformación, incluidos comentarios, texto de indentación y coordenadas de línea y columna.
Primer ejemplo con tokenize()
tokenize.tokenize() recibe una función readline que devuelve bytes.
from io import BytesIO
from tokenize import tokenize
fuente = b"total = precio * 2 # calculo\n"
for elemento in tokenize(BytesIO(fuente).readline):
print(elemento)El flujo incluye tokens como ENCODING, NAME, OP, NUMBER, COMMENT, NEWLINE y ENDMARKER.
La estructura TokenInfo
Cada elemento es una named tuple con estos campos:
type: tipo numérico;string: texto original;start: línea y columna inicial;end: línea y columna final;line: línea física de origen.
for elemento in tokenize(BytesIO(fuente).readline):
print(
elemento.type,
elemento.string,
elemento.start,
elemento.end,
)Las líneas comienzan en 1 y las columnas en 0. Las coordenadas sirven para resaltado, diagnósticos y patches de fuente.
Nombres legibles de tokens
El módulo token relaciona códigos numéricos con nombres.
import token
for elemento in tokenize(BytesIO(fuente).readline):
print(token.tok_name[elemento.type], repr(elemento.string))tokenize reexporta muchas constantes, pero el namespace separado deja clara la intención.
OP y exact_type
Los operadores y delimitadores se devuelven con el tipo genérico OP. La propiedad exact_type diferencia paréntesis, dos puntos, suma, multiplicación y otros símbolos.
for elemento in tokenize(BytesIO(b"x += 1\n").readline):
print(
token.tok_name[elemento.type],
token.tok_name[elemento.exact_type],
elemento.string,
)La documentación oficial de tokenize recomienda consultar exact_type cuando una herramienta necesita el operador preciso.
Los comentarios se conservan
A diferencia de varias interfaces del parser, tokenize devuelve comentarios.
from tokenize import COMMENT
comentarios = [
elemento
for elemento in tokenize(BytesIO(fuente).readline)
if elemento.type == COMMENT
]
for comentario in comentarios:
print(comentario.start, comentario.string)Esto permite construir formateadores, extractores de directivas, validadores de comentarios especiales y herramientas de documentación.
INDENT y DEDENT
La estructura de bloques aparece mediante INDENT y DEDENT.
codigo = b"if activo:\n ejecutar()\nfinalizar()\n"
for elemento in tokenize(BytesIO(codigo).readline):
print(token.tok_name[elemento.type], repr(elemento.string))INDENT contiene el whitespace original. DEDENT normalmente tiene una string vacía y señala el retorno a un nivel anterior.
NEWLINE frente a NL
NEWLINE completa una instrucción lógica. NL representa un salto físico que no termina la instrucción, como dentro de paréntesis o en una línea que solo contiene un comentario.
codigo = b"resultado = (\n 1 +\n 2\n)\n"
for elemento in tokenize(BytesIO(codigo).readline):
if elemento.type in {token.NEWLINE, token.NL}:
print(token.tok_name[elemento.type], elemento.start)La diferencia es esencial para formateadores que reorganizan líneas físicas sin cambiar el significado.
Leer strings Unicode con generate_tokens()
generate_tokens() recibe una función que devuelve str, no bytes.
from io import StringIO
from tokenize import generate_tokens
fuente_texto = "mensaje = 'hola'\n"
for elemento in generate_tokens(StringIO(fuente_texto).readline):
print(elemento)Esta API no genera el token ENCODING. Es conveniente cuando el texto ya fue decodificado correctamente.
Detectar el encoding
detect_encoding() lee como máximo dos líneas para encontrar un BOM UTF-8 o una cookie conforme a PEP 263.
from tokenize import detect_encoding
with open("programa.py", "rb") as archivo:
encoding, lineas = detect_encoding(archivo.readline)
print(encoding)
print(lineas)Si BOM y cookie no coinciden, lanza SyntaxError. Sin declaración, se usa UTF-8.
Abrir código fuente correctamente
tokenize.open() aplica la misma detección y devuelve un archivo en modo texto.
import tokenize
with tokenize.open("programa.py") as archivo:
contenido = archivo.read()Prefiere esta función cuando inspecciones archivos Python cuyo encoding todavía no conoces.
Reconstruir código con untokenize()
untokenize() recibe pares de tipo y string y reconstruye código fuente.
from tokenize import untokenize
pares = [
(elemento.type, elemento.string)
for elemento in tokenize(BytesIO(fuente).readline)
]
reconstruido = untokenize(pares)
print(reconstruido.decode("utf-8"))La garantía de round-trip conserva tipos y strings de tokens, pero el espaciado y las columnas pueden cambiar.
Transformar literales numéricos
Una herramienta puede reemplazar tokens numéricos sin alterar strings ni comentarios.
from tokenize import NUMBER, NAME, OP, STRING
resultado = []
for elemento in tokenize(BytesIO(b"tasa = 1.25\n").readline):
if elemento.type == NUMBER and "." in elemento.string:
resultado.extend([
(NAME, "Decimal"),
(OP, "("),
(STRING, repr(elemento.string)),
(OP, ")"),
])
else:
resultado.append((elemento.type, elemento.string))
print(untokenize(resultado).decode("utf-8"))Una transformación completa también debe añadir imports y considerar notación exponencial, complejos y underscores.
Renombrar identificadores con cuidado
Los tokens NAME pueden sustituirse, pero un renombrador correcto debe comprender el ámbito.
from tokenize import NAME
for elemento in tokenize(BytesIO(b"valor = valor + 1\n").readline):
if elemento.type == NAME and elemento.string == "valor":
nuevo = elemento._replace(string="contador")
else:
nuevo = elementoTokenize no sabe si un nombre es variable, atributo, import, parámetro o componente de pattern matching. Combínalo con AST y symtable para refactorización semántica.
Colorización de sintaxis
Un colorizador puede asociar clases visuales con tipos de token.
CLASES = {
token.NAME: "nombre",
token.NUMBER: "numero",
token.STRING: "string",
token.OP: "operador",
token.COMMENT: "comentario",
}
for elemento in generate_tokens(StringIO(fuente_texto).readline):
clase = CLASES.get(elemento.type, "otro")
renderizar(elemento.string, clase)Las keywords también llegan como NAME. Usa el módulo keyword para distinguirlas.
TokenError
TokenError aparece cuando una string multilínea o una expresión delimitada queda sin terminar al final del archivo.
from tokenize import TokenError
try:
list(tokenize(BytesIO(b"elementos = [1, 2\n").readline))
except TokenError as error:
print("Código incompleto:", error)Otros errores de sintaxis pueden atravesar la tokenización porque el scanner léxico no sustituye al parser.
Código sintácticamente inválido
La documentación advierte que el módulo está diseñado para fuente que también sería aceptada por ast.parse(). El comportamiento con código inválido es indefinido y puede cambiar.
Los editores que procesan archivos temporalmente incompletos necesitan recuperación tolerante, manejo de excepciones y no depender de una secuencia exacta después de la región inválida.
Uso desde la terminal
El módulo incluye una interfaz sencilla.
python -m tokenize programa.pyLa opción -e muestra nombres exactos de operadores.
python -m tokenize -e programa.pySin archivo, lee el código desde stdin.
Posiciones y Unicode
Las columnas se refieren a posiciones dentro de la string Python decodificada, no necesariamente a offsets de bytes del archivo original. Las herramientas que aplican patches en bytes necesitan un mapeo consciente del encoding.
Prueba tabs, caracteres combinantes, identificadores no ASCII y diferentes finales de línea.
Conservar el formato original
untokenize() no promete un espaciado idéntico. Esto es aceptable para un formateador, pero una corrección mínima puede necesitar editar directamente los rangos del texto original.
Usa coordenadas de tokens, aplica cambios desde el final hacia el principio y valida el resultado con ast.parse().
Seguridad y límites de recursos
Tokenizar no ejecuta la fuente, lo que es más seguro que importar un módulo. Un archivo enorme o diseñado para consumir recursos todavía puede gastar CPU y memoria.
Limita tamaño, tiempo y cantidad de tokens. Nunca concluyas que el código es seguro solo porque fue tokenizado o parseado.
Reporte estructurado
def reporte(fuente: bytes):
for elemento in tokenize(BytesIO(fuente).readline):
yield {
"tipo": token.tok_name[elemento.type],
"tipo_exacto": token.tok_name[elemento.exact_type],
"texto": elemento.string,
"inicio": elemento.start,
"fin": elemento.end,
}El reporte puede alimentar una tabla interactiva, visualizador de código o prueba de regresión.
Errores frecuentes
- Pasar bytes a
generate_tokens(). - Ignorar el token de encoding.
- Tratar todos los operadores únicamente como
OP. - Confundir
NLyNEWLINE. - Esperar que
untokenize()conserve espacios exactos. - Renombrar sin analizar ámbitos.
- Depender del comportamiento con código inválido.
- Usar tokenización como control de seguridad.
Buenas prácticas
- Usa bytes y
tokenize()para archivos completos. - Abre fuente con
tokenize.open(). - Consulta
exact_typepara operadores. - Combina tokens con AST y symtable.
- Valida el código reconstruido.
- Prueba comentarios, Unicode, tabs y estructuras multilínea.
- Aplica límites de recursos a entradas no confiables.
- Documenta qué detalles de formato conserva la herramienta.
Conclusión
El módulo tokenize en Python convierte el código fuente en un flujo rico que incluye comentarios, indentación, encoding, operadores y posiciones. Es una base sólida para colorizadores, formateadores, analizadores de estilo y transformaciones léxicas.
Su límite también es importante: los tokens describen la forma léxica, no el significado completo. Combinando tokenize con AST, symtable y validación posterior, una herramienta puede modificar código con precisión sin ejecutarlo ni confundir strings y comentarios con sintaxis activa.







