tokenize en Python: analiza código fuente

Publicado el: 04/08/2026
Tempo de leitura: 6 minutos
Código fuente y sintaxis que representan análisis léxico con tokenize en Python

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 = elemento

Tokenize 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.py

La opción -e muestra nombres exactos de operadores.

python -m tokenize -e programa.py

Sin 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 NL y NEWLINE.
  • 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_type para 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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Terminal de programación que representa compilación de entradas interactivas con codeop en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    codeop en Python: compila entradas interactivas

    Aprende codeop en Python para detectar entradas completas, compilar comandos de REPL y conservar __future__ con seguridad.

    Ler mais

    Tempo de leitura: 7 minutos
    04/08/2026
    Desarrollador analizando estructura de código y tablas de símbolos con symtable en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    symtable en Python: ámbitos y símbolos

    Aprende symtable en Python para analizar ámbitos, símbolos, globals, nonlocals, closures, imports, annotations y type parameters.

    Ler mais

    Tempo de leitura: 7 minutos
    03/08/2026
    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