profiletools

Lightweight decorators and context managers for profiling Python code.


Keywords
profiling, performance, line_profiler, cProfile, timing
License
BSD-3-Clause
Install
pip install profiletools==0.2.0

Documentation

profiletools

CI Tests PyPI Python Versions License: BSD-3-Clause Downloads

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:

  • @timefun for lightweight timing
  • TimeWith for timing arbitrary code blocks
  • @do_cprofile for function-level profiling with cProfile
  • @do_profile for line-by-line profiling with line_profiler

โœจ Features

  • timefun โ€” measure execution time of any function
  • TimeWith โ€” time code blocks with checkpoints
  • do_cprofile โ€” function-level profiling using cProfile
  • 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

๐Ÿค” 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_profiler installed.

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.