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

    Análisis de datos CSV con csv.QUOTE_STRINGS en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    csv.QUOTE_STRINGS: conserva tipos en archivos CSV

    Aprende csv.QUOTE_STRINGS en Python para citar texto, preservar tipos y crear archivos CSV más predecibles y seguros.

    Ler mais

    Tempo de leitura: 5 minutos
    15/09/2026
    Código y rutas de archivos para PurePath.full_match en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    PurePath.full_match: valida rutas con patrones glob

    Aprende PurePath.full_match en Python para validar rutas completas con patrones glob, controlar mayúsculas y crear filtros precisos.

    Ler mais

    Tempo de leitura: 6 minutos
    15/09/2026
    Código asíncrono que representa asyncio.eager_task_factory en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    asyncio.eager_task_factory: reduce overhead de tareas

    Aprende asyncio.eager_task_factory en Python para reducir overhead, entender cambios de orden y optimizar corrutinas cortas con seguridad.

    Ler mais

    Tempo de leitura: 5 minutos
    14/09/2026
    Desarrollador trabajando con timestamps UTC y calendar.timegm en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    calendar.timegm: convierte UTC a timestamp Unix

    Aprende calendar.timegm en Python para convertir fechas UTC en timestamps Unix y evitar errores de zona horaria y unidades.

    Ler mais

    Tempo de leitura: 6 minutos
    14/09/2026
    Programador analizando código para identificar tipos MIME de archivos con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    mimetypes.guess_file_type: detecta tipos MIME

    Aprende mimetypes.guess_file_type en Python para detectar tipos MIME en rutas, URLs, uploads y respuestas HTTP con fallbacks seguros.

    Ler mais

    Tempo de leitura: 4 minutos
    13/09/2026
    Componentes de servidor que representan intérpretes Python aislados ejecutándose en paralelo
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    InterpreterPoolExecutor: paralelismo real en Python

    Aprende InterpreterPoolExecutor en Python para tareas CPU-bound, intérpretes aislados, paralelismo real y concurrencia segura.

    Ler mais

    Tempo de leitura: 6 minutos
    13/09/2026