Lightweight, decorator-based profiling utilities for Python.
profiletools provides simple timing utilities, cProfile integration, and optional line-by-line profiling through line_profiler.
It provides a unified decorator-based interface for timing and profiling Python code and is designed to be:
- minimal
- dependency-light
- easy to use
- suitable for both quick diagnostics and deeper profiling
ย
from profiletools import timefun
ย
@timefun
def slow_function():
for i in range(100_000):
_ = i**2
slow_function()Output:
@timefun:slow_function took 0.001234 seconds
Choose the tool that matches your needs:
-
@timefunfor lightweight timing -
TimeWithfor timing arbitrary code blocks -
@do_cprofilefor function-level profiling withcProfile -
@do_profilefor line-by-line profiling withline_profiler
-
timefunโ measure execution time of any function -
TimeWithโ time code blocks with checkpoints -
do_cprofileโ function-level profiling usingcProfile -
do_profileโ line-by-line profiling (optional dependency:line_profiler)
Supports profiling:
- standalone functions
- class methods
- additional functions via
follow= - all methods of a class via
follow_all_methods=True - direct decorator application or manual wrapping
ย
profiletools provides a simple decorator-based interface on top of
Python's profiling tools.
ย
| Tool | Purpose |
|---|---|
timefun |
Lightweight timing of functions |
TimeWith |
Timing code blocks with checkpoints |
do_cprofile |
Easy integration with cProfile
|
do_profile |
Line-by-line profiling using line_profiler
|
Use this quick decision tree to choose the right profiling tool for your task:
Start
โโโ Need simple timing?
โ โโโ Time a function โ timefun
โ โโโ Time a block โ TimeWith
โ โโโ Single-expression micro-benchmark โ timeit
โ โโโ Multi-statement micro-benchmark โ timerit
โ
โโโ Need per-line detail?
โ โโโ LineProfiler / @do_profile
โ
โโโ Need per-function detail?
โ โโโ cProfile / @do_cprofile
โ
โโโ Need whole-program insight?
โ โโโ Call-stack timeline โ PyInstrument
โ โโโ CPU+GPU+memory โ Scalene
โ
โโโ Profiling threads/async/gevent?
โ โโโ Yappi
โ
โโโ Profiling PyTorch GPU/autograd?
โ โโโ torch.profiler
โ
โโโ Want decorator-based targeted profiling?
โโโ profiletools
See the External Profilers section below for links and descriptions.
- LineProfiler โ line-by-line CPU profiler
- Scalene โ CPU+GPU+memory sampling profiler
- PyInstrument โ call-stack sampling profiler
- Yappi โ tracing profiler for multithreading, asyncio, gevent
- cProfile โ builtin function-level profiler
- timeit โ builtin micro-benchmarking tool
- timerit โ multi-statement micro-benchmarking
- torch.profiler โ PyTorch GPU & operator-level profiler
pip install profiletoolspip install profiletools[line]The line_profiler dependency is optional and only required when using @do_profile.
from profiletools import timefun
@timefun
def expensive_function():
for x in range(50000):
i = x**3
return i
expensive_function()Output:
@timefun:expensive_function took 0.012345 seconds
from profiletools import TimeWith
with TimeWith("expensive block") as timer:
for x in range(50000):
i = x**3
timer.checkpoint("halfway done")
for x in range(50000):
i = x**4
timer.checkpoint("finished second part")Example output:
expensive block halfway done took 0.123456 seconds
expensive block finished second part took 0.234567 seconds
expensive block finished took 0.234890 seconds
import time
from profiletools import do_cprofile
def calculate(x):
time.sleep(0.1)
return x**3
@do_cprofile()
def expensive_function():
for x in range(10):
i = calculate(x)
return i
expensive_function()Produces output similar to:
ncalls tottime percall cumtime percall filename:lineno(function)
10 0.000 0.000 1.003 0.100 demo.py:46(calculate)
1 0.000 0.000 1.003 1.003 demo.py:50(expensive_function)
10 1.003 0.100 1.003 0.100 {built-in method time.sleep}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
Requires
line_profilerinstalled.
from profiletools import do_profile
def helper():
yield from range(5000)
@do_profile(follow=[helper])
def expensive_function():
for x in helper():
i = x**3
return i
expensive_function()Output includes both functions:
Function: expensive_function at line 63
Line # Hits Time Per Hit % Time Line Contents
==============================================================
63 @do_profile(follow=[helper])
64 def expensive_function():
65 5001 28256.0 5.7 65.2 for x in helper():
66 5000 15103.0 3.0 34.8 i = x**3
67 1 3.0 3.0 0.0 return i
Function: helper at line 59
Line # Hits Time Per Hit % Time Line Contents
==============================================================
59 def helper():
60 1 49.0 49.0 100.0 yield from range(5000)
from profiletools import do_profile
class Worker:
@do_profile(follow=["_numbers"])
def compute(self):
for x in self._numbers():
i = x**4
return i
def _numbers(self):
yield from range(5000)
Worker().compute()from profiletools import do_profile
class Worker:
@do_profile(follow_all_methods=True)
def compute(self):
for x in self._numbers():
for y in self._small_numbers():
i = x ^ y
return i
def _numbers(self):
yield from range(5000)
def _small_numbers(self):
yield from range(50)
Worker().compute()This automatically profiles:
compute_numbers_small_numbers
Function: Worker.compute at line 100
Line # Hits Time Per Hit % Time Line Contents
==============================================================
100 @do_profile(follow_all_methods=True)
101 def compute(self):
102 5001 36921.0 7.4 1.4 for x in self._numbers():
103 255000 1760586.0 6.9 66.9 for y in self._small_numbers():
104 250000 835163.0 3.3 31.7 i = x ^ y
105 1 5.0 5.0 0.0 return i
Total time: 4.3e-06 s
Function: Worker._numbers at line 107
Line # Hits Time Per Hit % Time Line Contents
==============================================================
107 def _numbers(self):
108 1 43.0 43.0 100.0 yield from range(5000)
Total time: 0.003679 s
Function: Worker._small_numbers at line 110
Line # Hits Time Per Hit % Time Line Contents
==============================================================
110 def _small_numbers(self):
111 5000 36790.0 7.4 100.0 yield from range(50)
from profiletools import do_profile
class Worker:
def compute(self):
for x in self._numbers():
i = x**3
return i
def _numbers(self):
yield from range(5000)
worker = Worker()
do_profile(follow=[worker._numbers])(worker.compute)()Will profile:
compute_numbers
This project is licensed under the BSD-3-Clause License. See the LICENSE file for details.
Pull requests are welcome.
If you discover a bug or would like to propose an enhancement, please open an issue or submit a pull request.