Scripts Python lentos: profiling y optimización

Actualizado el: 20/08/2026
Tempo de leitura: 5 minutos
Dicas para otimizar scripts Python lentos

Un script Python lento no siempre necesita una técnica avanzada. Muchas veces el problema está en un bucle innecesario, una estructura de datos incorrecta, demasiadas lecturas de disco, solicitudes de red repetidas o cálculos que se ejecutan una y otra vez. La forma profesional de mejorar el rendimiento consiste en medir, identificar el cuello de botella y cambiar solo lo que realmente importa.

Primero define qué significa lento

Antes de optimizar, establece un escenario reproducible. ¿El script tarda diez segundos, diez minutos o varias horas? ¿La lentitud aparece al iniciar, durante una consulta, al procesar archivos o al guardar resultados? Sin una medición inicial no podrás demostrar que una modificación mejoró el programa.

Medir con perf_counter

Para una medición sencilla:

from time import perf_counter

inicio = perf_counter()
ejecutar_tarea()
duracion = perf_counter() - inicio
print(f"Duración: {duracion:.3f} segundos")

Repite varias veces con la misma entrada. Una sola ejecución puede estar afectada por cachés, procesos del sistema o latencia externa.

Localizar cuellos de botella con cProfile

Cuando el programa contiene muchas funciones, utiliza un perfilador:

python -m cProfile -s cumulative mi_script.py

La salida indica cuántas veces se llamó cada función, el tiempo interno y el tiempo acumulado. La guía de cProfile en Python explica cómo interpretar estos valores. La documentación oficial de los perfiladores describe cProfile, profile y pstats.

Usar timeit para fragmentos pequeños

timeit sirve para comparar alternativas cortas evitando parte del ruido:

from timeit import repeat

resultados = repeat(
    "sum(range(1000))",
    repeat=5,
    number=10_000,
)
print(min(resultados))

No utilices un microbenchmark para concluir cómo se comportará una aplicación completa. La documentación oficial de timeit explica parámetros, línea de comandos y buenas prácticas.

Elegir la estructura de datos correcta

Buscar repetidamente en una lista puede ser costoso. Si solo necesitas comprobar pertenencia, un conjunto suele ser más apropiado:

usuarios_validos = {"ana", "luis", "marta"}

if nombre in usuarios_validos:
    autorizar()

Listas, conjuntos y diccionarios tienen propiedades diferentes. La guía de listas en Python ayuda a elegir una colección adecuada.

Evitar trabajo repetido dentro de bucles

Mueve fuera del bucle los cálculos que no cambian:

# menos eficiente
for producto in productos:
    impuesto = obtener_tasa_configurada()
    guardar(producto.precio * impuesto)

# mejor
impuesto = obtener_tasa_configurada()
for producto in productos:
    guardar(producto.precio * impuesto)

La mejora real depende del costo de obtener_tasa_configurada, pero el principio es evitar repetir trabajo constante.

Usar funciones incorporadas

Funciones como sum, min, max, any y all suelen ser claras y eficientes:

total = sum(venta.total for venta in ventas)
existe_error = any(item.error for item in resultados)

No reemplaces código legible por trucos difíciles de mantener solo por una diferencia mínima.

Comprensiones y generadores

Una comprensión puede ser más compacta que un bucle que solo construye una lista:

cuadrados = [n * n for n in numeros]

Si solo recorrerás los valores una vez, un generador evita almacenar toda la colección:

cuadrados = (n * n for n in numeros)
for valor in cuadrados:
    procesar(valor)

Esto es especialmente importante cuando existe riesgo de MemoryError.

Reducir operaciones de entrada y salida

Leer y escribir en disco suele ser mucho más lento que una operación en memoria. En lugar de abrir un archivo por cada línea, abre una sola vez:

with open("salida.txt", "w", encoding="utf-8") as archivo:
    for linea in lineas:
        archivo.write(linea + "\n")

Para bases de datos, agrupa inserciones y evita una consulta por elemento. Para APIs, considera paginación, reutilización de conexiones y límites de concurrencia.

No confundir CPU con espera

Una tarea intensiva en CPU necesita mejores algoritmos, bibliotecas optimizadas o procesos paralelos. Una tarea que espera red, disco o base de datos puede beneficiarse de concurrencia o asincronía. Aplicar asyncio a cálculos pesados no los vuelve automáticamente más rápidos.

Complejidad algorítmica

Un algoritmo cuadrático puede funcionar con cien elementos y colapsar con un millón:

# comparación repetida costosa
repetidos = []
for valor in datos:
    if datos.count(valor) > 1:
        repetidos.append(valor)

# enfoque más apropiado
from collections import Counter
conteos = Counter(datos)
repetidos = [valor for valor, cantidad in conteos.items() if cantidad > 1]

La biblioteca estándar ofrece soluciones preparadas para muchos patrones. El artículo sobre itertools muestra cómo construir iteraciones eficientes.

Caché con límites

Si una función pura repite cálculos con los mismos argumentos, lru_cache puede ayudar:

from functools import lru_cache

@lru_cache(maxsize=256)
def calcular_precio(codigo):
    return consulta_costosa(codigo)

La caché debe tener un límite y una estrategia coherente. Una caché ilimitada puede convertirse en un problema de memoria.

NumPy y bibliotecas compiladas

Para grandes operaciones numéricas, NumPy suele ejecutar bucles en código compilado. Evita aplicar una función Python elemento por elemento cuando exista una operación vectorizada. Sin embargo, medir sigue siendo necesario: crear muchas copias de arrays también tiene costo.

Python compilado y Cython

Cuando el cuello de botella está en cálculos Python que no pueden vectorizarse, Cython puede añadir tipos estáticos y compilar partes críticas. Revisa la guía para convertir Python con Cython. Esta opción añade complejidad de compilación y no debe ser el primer paso.

Funciones pequeñas y medibles

Separar el programa en funciones claras permite perfilar cada etapa, probar cambios y evitar efectos inesperados. Una función gigantesca dificulta saber dónde se consume el tiempo.

Errores frecuentes al optimizar

Los errores más comunes son optimizar sin medir, elegir siempre la ejecución más rápida de una prueba inestable, ignorar la base de datos, sacrificar legibilidad por microsegundos y aumentar concurrencia sin límites. También es un error comparar dos alternativas con entradas diferentes.

Flujo recomendado

Define una carga representativa, registra el tiempo, perfila el programa, selecciona el cuello de botella principal y formula una hipótesis. Haz un cambio pequeño, ejecuta pruebas de corrección y vuelve a medir. Conserva el cambio solo si aporta una mejora relevante sin deteriorar mantenimiento o memoria.

Un script rápido no nace de aplicar todos los trucos disponibles. Nace de eliminar el trabajo innecesario en la parte que realmente domina el tiempo total. Medir antes y después convierte la optimización en ingeniería, no en intuición.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Configuración TOML en un proyecto Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    tomllib en Python: lee archivos TOML

    Aprende a leer TOML con tomllib en Python, validar configuraciones, gestionar errores y organizar proyectos con pyproject.toml.

    Ler mais

    Tempo de leitura: 6 minutos
    24/07/2026
    Desarrollador monitoreando registros estructurados en Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Observabilidad en Python con structlog

    Aprende observabilidad en Python con structlog, eventos JSON, contexto, pruebas, control de volumen y recomendaciones para producción.

    Ler mais

    Tempo de leitura: 5 minutos
    24/07/2026
    Configuración segura de aplicaciones Python con Pydantic Settings
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Pydantic Settings: configuración segura

    Aprende a validar variables de entorno, organizar configuraciones y proteger secretos en proyectos Python con Pydantic Settings.

    Ler mais

    Tempo de leitura: 5 minutos
    23/07/2026
    Desenvolvedor programando em Python com Ruff para lint e formatação
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Ruff: lint y formato de código Python

    Aprende Ruff en Python para lint, formato, correcciones automáticas, pyproject.toml, VS Code y CI con una configuración práctica.

    Ler mais

    Tempo de leitura: 10 minutos
    22/07/2026
    Dicas para melhorar performance de scripts Python lentos
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    ¿Por qué Python es lento? Causas y optimización

    Descubre por qué Python puede ser lento y mejora su rendimiento con cProfile, algoritmos, sets, generadores, NumPy, caché y concurrencia.

    Ler mais

    Tempo de leitura: 5 minutos
    12/07/2026
    Leitura segura de senhas no terminal usando Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    getpass: lee contraseñas en terminal con Python

    Lee contraseñas de forma segura con getpass, valida entradas, evita logs y texto plano y almacena credenciales con hashing adecuado.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026