Por qué Python puede ser lento y cómo mejorar su rendimiento

Publicado el: 12/07/2026
Tempo de leitura: 5 minutos
Dicas para melhorar performance de scripts Python lentos

Python prioriza la legibilidad, la productividad y un ecosistema amplio. Esa elección puede hacerlo más lento que un programa compilado en determinadas tareas, pero no significa que todos los proyectos necesiten optimización. La mayoría de aplicaciones pierde tiempo en algoritmos inadecuados, consultas repetidas, acceso a disco, red o estructuras de datos incorrectas. Antes de cambiar de lenguaje, mide el problema y optimiza la parte que realmente consume recursos.

Para profundizar, consulta la guía de cProfile en Python, el artículo sobre timeit, la introducción a NumPy, la guía de list comprehensions y el tutorial sobre sets.

Las referencias externas principales son la documentación oficial de los profilers de Python y la documentación oficial de timeit.

Python no siempre es el cuello de botella

Un script puede parecer lento porque espera una API, abre miles de archivos pequeños, ejecuta consultas individuales o descarga datos sin reutilizar conexiones. En esos casos, la velocidad del intérprete tiene menos importancia que la arquitectura del proceso.

Medir el tiempo total

from time import perf_counter

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

perf_counter() es apropiado para medir intervalos cortos. Repite la prueba con datos representativos y evita sacar conclusiones a partir de una única ejecución.

Perfilar con cProfile

python -m cProfile -s cumulative programa.py

El informe muestra cantidad de llamadas y tiempo acumulado por función. Ordenar por tiempo acumulado ayuda a detectar funciones que llaman a otras operaciones costosas.

import cProfile
import pstats

with cProfile.Profile() as perfil:
    ejecutar_proceso()

estadisticas = pstats.Stats(perfil)
estadisticas.sort_stats("cumulative").print_stats(20)

No optimizar sin evidencia

Si una función representa menos del uno por ciento del tiempo total, hacerla dos veces más rápida apenas cambia la experiencia. Prioriza las operaciones dominantes y conserva pruebas para asegurar que la optimización no modifica los resultados.

Elegir un algoritmo mejor

def contiene_duplicados(valores):
    vistos = set()
    for valor in valores:
        if valor in vistos:
            return True
        vistos.add(valor)
    return False

Buscar repetidamente en un set suele ser mucho más eficiente que recorrer una lista completa para cada elemento. Cambiar la estructura puede producir una mejora mayor que reescribir el mismo bucle.

Evitar bucles anidados innecesarios

# Enfoque costoso
coincidencias = []
for usuario in usuarios:
    for permiso in permisos:
        if usuario["permiso_id"] == permiso["id"]:
            coincidencias.append((usuario, permiso))

Crea un índice:

permisos_por_id = {p["id"]: p for p in permisos}
coincidencias = []

for usuario in usuarios:
    permiso = permisos_por_id.get(usuario["permiso_id"])
    if permiso is not None:
        coincidencias.append((usuario, permiso))

Utilizar funciones integradas

total = sum(valores)
mayor = max(valores)
ordenados = sorted(valores)

Las funciones integradas suelen estar optimizadas y expresan mejor la intención que una implementación manual. No reemplaces código claro por trucos difíciles de leer para ahorrar una cantidad insignificante.

Comprensiones con moderación

cuadrados_pares = [n * n for n in numeros if n % 2 == 0]

Una comprensión sencilla puede ser más compacta y eficiente que añadir elementos manualmente. Si contiene varias condiciones, llamadas o bucles anidados, una función y un bucle normal serán más mantenibles.

Generadores para reducir memoria

cuadrados = (n * n for n in range(10_000_000))

for valor in cuadrados:
    procesar(valor)

El generador produce valores bajo demanda. Esto no siempre reduce el tiempo, pero evita construir una lista enorme y puede permitir que el proceso comience antes.

Procesar archivos por líneas

with open("registro.log", encoding="utf-8") as archivo:
    for linea in archivo:
        analizar(linea)

Leer todo con read() puede consumir memoria y provocar pausas. Para transformaciones tabulares grandes, utiliza bloques o herramientas diseñadas para streaming.

Reducir operaciones de entrada y salida

# Mejor que escribir una línea abriendo el archivo cada vez
with open("salida.txt", "w", encoding="utf-8") as archivo:
    archivo.writelines(lineas)

Agrupa escrituras y reutiliza conexiones. El acceso a disco y red suele ser mucho más lento que operaciones en memoria.

Reutilizar sesiones HTTP

import requests

with requests.Session() as sesion:
    for url in urls:
        respuesta = sesion.get(url, timeout=20)
        respuesta.raise_for_status()

Una sesión puede reutilizar conexiones. Respeta límites del servicio y evita paralelizar solicitudes sin autorización.

Optimizar consultas a bases de datos

Evita una consulta por cada fila. Utiliza parámetros, operaciones por lotes, índices adecuados y selecciona solo las columnas necesarias. Mide el plan de ejecución en la base de datos; modificar Python no arregla una consulta mal diseñada.

Caché para resultados repetidos

from functools import lru_cache

@lru_cache(maxsize=256)
def calcular_configuracion(clave):
    return operacion_costosa(clave)

La caché funciona cuando la misma entrada produce el mismo resultado y se repite. No almacenes indefinidamente datos enormes, sensibles o que cambian con frecuencia. Revisa cache_info() para confirmar que existen aciertos.

NumPy para operaciones numéricas

import numpy as np

valores = np.array([1, 2, 3, 4], dtype=np.float64)
resultado = valores * 1.18

NumPy ejecuta operaciones vectorizadas en código compilado y es apropiado para arrays numéricos homogéneos. Convertir listas pequeñas a arrays en cada función puede añadir más coste que beneficio.

Pandas sin apply innecesario

df["total"] = df["cantidad"] * df["precio"]

Las operaciones vectorizadas suelen ser preferibles a aplicar una función Python por fila. Para archivos grandes, limita columnas, define tipos y procesa en bloques.

Concurrencia para tareas de espera

Threads y asyncio pueden mejorar tareas que esperan red, disco o servicios externos. No aceleran automáticamente cálculos intensivos de CPU debido a limitaciones del intérprete tradicional y al coste de coordinación.

Multiprocessing para CPU

from concurrent.futures import ProcessPoolExecutor

with ProcessPoolExecutor() as ejecutor:
    resultados = list(ejecutor.map(calculo_intensivo, datos))

Los procesos permiten utilizar varios núcleos, pero deben serializar datos y consumen memoria. Utilízalos para tareas suficientemente grandes, independientes y medibles.

Compilar partes críticas

Cython, Numba, extensiones en C o Rust y otras herramientas pueden acelerar bucles numéricos críticos. Son una etapa posterior: primero mejora algoritmo, estructura y acceso a datos. Añadir una compilación aumenta complejidad de desarrollo y distribución.

Comparar con timeit

from timeit import repeat

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

Compara funciones equivalentes con los mismos datos. No incluyas configuración, impresión o lectura de archivos si solo quieres medir el núcleo del algoritmo.

Vigilar la memoria

Un programa puede volverse lento por paginación de memoria, copias repetidas o acumulación de objetos. Usa generadores, libera referencias innecesarias y evita duplicar DataFrames enormes. La solución no es llamar constantemente al recolector de basura.

Establecer un objetivo

Define qué significa “suficientemente rápido”: tiempo máximo de respuesta, cantidad de archivos por minuto o consumo de memoria. Sin un objetivo, la optimización puede continuar indefinidamente sin valor para el usuario.

Errores frecuentes

Los errores habituales son optimizar antes de perfilar, comparar una sola ejecución, utilizar time.time() para mediciones pequeñas, sustituir claridad por trucos, paralelizar tareas diminutas y creer que una comprensión siempre es más rápida. También es común ignorar base de datos, red y disco.

Conclusión

Python puede ser más lento en ciertos cálculos, pero la mayoría de mejoras comienza con medición, algoritmos y estructuras de datos. Perfila con cProfile, compara microoperaciones con timeit, reduce I/O y utiliza NumPy o concurrencia cuando el tipo de trabajo lo justifique. Optimiza únicamente las secciones que dominan el tiempo total y conserva pruebas para garantizar resultados correctos.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Leitura de arquivos grandes em Python sem travar o sistema
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Cómo leer archivos gigantes en Python sin bloquear el programa

    Aprende a leer archivos gigantes en Python por líneas, bloques y chunks, controlar memoria, procesar CSV y JSON y reanudar

    Ler mais

    Tempo de leitura: 5 minutos
    12/07/2026
    Leitura de variáveis de ambiente em projetos Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Cómo leer variables de entorno en Python de forma segura

    Aprende a leer variables de entorno con os.getenv, validar tipos, usar .env con seguridad y evitar que secretos aparezcan en

    Ler mais

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

    Cómo leer contraseñas de forma segura en el 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
    Exemplo de testes unitários em Python com código de unittest para validação automatizada
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Tests unitarios en Python: unittest, pytest y mocks

    Aprende tests unitarios en Python con unittest, pytest, fixtures, parametrización, mocks, cobertura y buenas prácticas para evitar regresiones.

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Proteção de API Flask usando autenticação JWT em Python
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Cómo proteger una API Flask con JWT

    Protege una API Flask con JWT, access y refresh tokens, bcrypt, roles, revocación, variables de entorno, HTTPS y pruebas de

    Ler mais

    Tempo de leitura: 5 minutos
    11/07/2026
    Buenas Prácticas
    Foto de perfil de Leandro Hirt da Academify

    Enums en Python: evita valores mágicos y errores

    Aprende enums en Python con Enum, IntEnum, StrEnum, auto, unique, Flag, validación, JSON, bases de datos y buenas prácticas.

    Ler mais

    Tempo de leitura: 4 minutos
    11/07/2026