El módulo pstats en Python lee, combina, ordena, filtra y presenta los datos producidos por cProfile y profile. El profiler registra llamadas, retornos y excepciones; pstats convierte esa información en reportes que ayudan a localizar árboles de llamadas lentos, invocaciones excesivas y algoritmos inadecuados.
Profiling no es benchmarking. El profiler determinista añade overhead y observa una ejecución completa. Usa timeit para comparar snippets pequeños en condiciones controladas y pstats para entender dónde gastó tiempo una ejecución representativa.
Genera un archivo de perfil
python -m cProfile -o perfil.prof aplicacion.pyPara perfilar un módulo:
python -m cProfile -o perfil.prof -m mi_paquete.cliEl dump no es un formato estable de intercambio. Analízalo con la misma versión, implementación y preferiblemente sistema operativo que lo creó.
Abre los datos con Stats
import pstats
estadisticas = pstats.Stats("perfil.prof")
estadisticas.print_stats()El constructor también acepta un objeto cProfile.Profile, varios archivos o un stream personalizado.
Comprende las columnas
ncalls: cantidad total de llamadas.tottime: tiempo dentro de la función, excluyendo subllamadas.percall:tottimedividido por llamadas.cumtime: tiempo en la función y todo lo llamado debajo.percallacumulado: tiempo acumulado dividido por llamadas primitivas.
Las funciones recursivas pueden mostrar total/primitivas.
Ordena por tiempo acumulado
SortKey.CUMULATIVE muestra funciones cuyos árboles completos consumieron más tiempo.
from pstats import Stats, SortKey
Stats("perfil.prof") \
.sort_stats(SortKey.CUMULATIVE) \
.print_stats(20)Suele ser la mejor primera vista para encontrar endpoints, tareas o algoritmos de alto nivel costosos.
Ordena por tiempo interno
SortKey.TIME clasifica el tiempo gastado directamente en el cuerpo, sin incluir callees.
Stats("perfil.prof") \
.sort_stats(SortKey.TIME) \
.print_stats(20)Un cumtime alto con tottime bajo indica delegación. Si ambos son altos, la función hace trabajo importante directamente.
Usa SortKey
La enumeración es más robusta que abreviaturas. Incluye CALLS, PCALLS, FILENAME, LINE, NAME, NFL, STDNAME, TIME y CUMULATIVE.
estadisticas.sort_stats(
SortKey.NAME,
SortKey.FILENAME,
SortKey.LINE,
)Las claves adicionales resuelven empates. Evita la interfaz numérica antigua.
Elimina prefijos de directorio
strip_dirs() acorta el reporte:
estadisticas.strip_dirs() \
.sort_stats(SortKey.TIME) \
.print_stats()El método modifica el objeto y puede fusionar entradas que se vuelven indistinguibles. Conserva otra instancia con rutas completas si son necesarias.
Restringe la salida
print_stats() acepta cantidad, fracción y expresiones regulares.
estadisticas.sort_stats(SortKey.CUMULATIVE)
estadisticas.print_stats(30)
estadisticas.print_stats(0.10)
estadisticas.print_stats("mi_paquete")Las restricciones se aplican en orden. Reducir a la mitad y después filtrar es distinto de filtrar primero.
Inspecciona callers
print_callers() muestra qué funciones invocaron cada entrada.
estadisticas.print_callers("procesar_pedido")Puede revelar un helper barato que se volvió costoso porque muchos caminos lo llaman repetidamente.
Inspecciona callees
print_callees() muestra la dirección opuesta.
estadisticas.print_callees("procesar_pedido")Usa callers para localizar la demanda y callees para descomponer el trabajo interno.
Combina varias ejecuciones
El constructor puede combinar varios dumps. Las funciones con el mismo archivo, línea y nombre se acumulan.
estadisticas = pstats.Stats(
"worker-1.prof",
"worker-2.prof",
"worker-3.prof",
)Añade resultados posteriores con add():
estadisticas.add("worker-4.prof")Combina solo ejecuciones comparables. Mezclar cargas, versiones, configuraciones o máquinas produce conclusiones engañosas.
Compara antes y después
pstats agrega archivos, pero no calcula automáticamente un delta estadístico entre versiones. Genera reportes equivalentes, exporta datos estructurados y compara funciones seleccionadas.
Repite escenarios, controla el tamaño de entrada y registra commit, entorno, versión y efectos de calentamiento.
Captura la salida
import io
import pstats
from pstats import SortKey
buffer = io.StringIO()
pstats.Stats("perfil.prof", stream=buffer) \
.sort_stats(SortKey.CUMULATIVE) \
.print_stats(20)
reporte = buffer.getvalue()Sirve para artefactos de CI y dashboards. Elimina rutas absolutas y nombres sensibles antes de publicar.
Analiza un Profile en memoria
import cProfile
import pstats
profiler = cProfile.Profile()
profiler.enable()
ejecutar_escenario()
profiler.disable()
pstats.Stats(profiler) \
.sort_stats(pstats.SortKey.CUMULATIVE) \
.print_stats(15)Esto evita archivos temporales. Guardar el dump sigue siendo útil para procesos largos y revisión posterior.
Exporta datos estructurados
get_stats_profile() devuelve una StatsProfile con registros FunctionProfile.
perfil = estadisticas.get_stats_profile()
for nombre, funcion in perfil.func_profiles.items():
print(nombre, funcion.cumulative_time)Facilita JSON y tablas. Los nombres pueden colisionar, por lo que conviene conservar archivo y línea.
Navegador interactivo
Ejecutar pstats como módulo abre una interfaz de línea:
python -m pstats perfil.profPermite cargar, ordenar, filtrar y mostrar callers o callees. Utiliza conceptos de la guía de cmd en Python.
Tiempo acumulado y algoritmos
Una función con alto tiempo acumulado puede elegir un algoritmo caro, repetir I/O o invocar demasiado una dependencia. Microoptimizar sus propias líneas no siempre ayuda. Reducir trabajo, llamadas, asignaciones y viajes de red suele importar más.
Cantidad de llamadas
SortKey.CALLS destaca funciones muy invocadas.
estadisticas.sort_stats(SortKey.CALLS).print_stats(20)Un helper mínimo puede dominar el tiempo si se llama millones de veces. Conteos inesperados también revelan callbacks duplicados o loops accidentales.
Tiempo y memoria por separado
pstats analiza llamadas y tiempo, no asignaciones. Para crecimiento de memoria, compara snapshots con tracemalloc en Python. Ejecuta ambas herramientas por separado cuando sea posible.
No confundas perfil con bytecode
Los reportes se organizan por funciones, archivos y líneas. Para instrucciones compiladas, consulta opcode en Python y dis en Python.
Rendimiento de imports
Si el perfil muestra imports costosos, modulefinder en Python ayuda a mapear dependencias estáticas. Los imports dinámicos y efectos superiores requieren medición real.
Limitaciones
- El profiler añade overhead.
- Funciones C pueden parecer demasiado rápidas.
- Llamadas muy breves acumulan error.
- Una ejecución puede no representar producción.
- El I/O externo varía con el entorno.
- Threads y procesos requieren recolección planificada.
Usa el perfil para formular hipótesis y confirma mejoras con mediciones repetidas.
Protege los dumps
Los archivos pueden revelar rutas, módulos, arquitectura, funciones de negocio y patrones de uso. Almacénalos con control de acceso y no cargues dumps desconocidos en entornos sensibles.
El formato no garantiza compatibilidad futura y no debe convertirse en API pública.
Buenas prácticas
- Empieza por
CUMULATIVEy luego usaTIME. - Filtra al código de la aplicación.
- Revisa callers y callees.
- Repite cargas representativas.
- Registra entorno y commit.
- No trates profiling como benchmark exacto.
- Protege archivos de perfil.
- Mide nuevamente después de optimizar.
Conclusión
pstats en Python transforma datos del profiler en reportes accionables. Orden, filtros, agregación, callers, callees y perfiles estructurados ayudan a distinguir funciones directamente lentas de costos causados por delegación o frecuencia.
Consulta la documentación oficial de pstats y la documentación de timeit.







