profiletools
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
🚀 Quick Start
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 timingTimeWithfor timing arbitrary code blocks@do_cprofilefor function-level profiling withcProfile@do_profilefor line-by-line profiling withline_profiler
✨ Features
timefun— measure execution time of any functionTimeWith— time code blocks with checkpointsdo_cprofile— function-level profiling usingcProfiledo_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
🤔 Why profiletools?
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 |
🔍 Profiler Decision Tree (Quick Guide)
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.
🔗 External Profilers (with homepage links)
- 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
📥 Installation
Basic installation
pip install profiletools
Enable line-by-line profiling
pip install profiletools[line]
The line_profiler dependency is optional and only required when using @do_profile.
📚 Usage Examples
⏱️ Timing a function with @timefun
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
⏱️ Timing a block with TimeWith
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
🧵 Profiling a function with @do_cprofile
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}
📊 Line-by-line profiling with @do_profile
Requires
line_profilerinstalled.
Profile a function and follow another function
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)
📦 Profiling class methods
Follow a class method by name
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()
🧩 Profile all methods of a class
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)
🔧 Using do_profile without a decorator
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
📄 License
This project is licensed under the BSD-3-Clause License. See the LICENSE file for details.
🤝 Contributing
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.
Release files for profiletools 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| profiletools-0.2.0.tar.gz | 14.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| profiletools-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 25.1 kB
Release files / profiletools-0.2.0.tar.gz
| Download URL | profiletools-0.2.0.tar.gz |
|---|---|
| Size | 14.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e24f2dd47438e4def7c69394207cda8c753a145fabd286bc8f6c7b27aaeab80f
|
|
BLAKE2b-256 checksum How to use checksums |
2837e12b3151d8ceb97bc28fb4f1425a3d5f1d2964c8c93414b86918df13f132
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 22, 2026.
Transparency logRelease files / profiletools-0.2.0-py3-none-any.whl
| Download URL | profiletools-0.2.0-py3-none-any.whl |
|---|---|
| Size | 10.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eb55d0b9bd26d250d740043e768d9b0f9e94ac0bb6ce26790b9b0ab5516f056d
|
|
BLAKE2b-256 checksum How to use checksums |
57230e3d410e5380be60f4ce70adb55d26f9965af40e476e23388706bec4d7ad
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 22, 2026.
Transparency log