I have created a cumulative profiler, meaning it runs a function several times in a row and prints the cumulative profiler results.

it's python >= 3.6 specific, but you can remove nonlocal and the type hints for it work on older versions.

import cProfile, pstats
from typing import Union, List, Callable, Optional, Tuple, Any


class ProfileFunc:
    def __init__(self, func: Callable, sort_stats_by: str):
        self.func: Callable = func
        self.profile_runs: List[cProfile.Profile] = []
        self.sort_stats_by: str = sort_stats_by

    def __call__(self, *args, **kwargs) -> Tuple[Any, pstats.Stats]:
        pr = cProfile.Profile()
        pr.enable()  # this is the profiling section
        retval = self.func(*args, **kwargs)

        ps = pstats.Stats(*self.profile_runs).sort_stats(self.sort_stats_by)
        return retval, ps

def cumulative_profiler(amount_of_times: Optional[Union[Callable, int]] = DEFAULT_AMOUNT,
                        sort_stats_by: str='time') -> Callable:

    def real_decorator(func: Callable):
        def wrapper(*args, **kwargs):
            nonlocal func, amount_of_times, sort_stats_by    

            profiled_func = ProfileFunc(func, sort_stats_by)
            assert amount_of_times > 0, 'Cant profile for less then 1 run'
            for i in range(amount_of_times):
                retval, ps = profiled_func(*args, **kwargs)
            return retval  # returns the results of the function

        return wrapper

    # in case you don't want to specify the amount of times
    if callable(amount_of_times):
        the_func = amount_of_times  # amount_of_times is the function in here
        global DEFAULT_AMOUNT
        amount_of_times = DEFAULT_AMOUNT
        return real_decorator(the_func)
    return real_decorator


profiling the function baz

import time

def baz():
    return 1


baz ran 5 times and printed this:

         20 function calls in 15.003 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
       10   15.003    1.500   15.003    1.500 {built-in method time.sleep}
        5    0.000    0.000   15.003    3.001 <ipython-input-9-c89afe010372>:3(baz)
        5    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

specifying the amount of times

def baz():

One small problem is that pstats.Stats is called each time instead of only one time in the end. however I don't think it's an expensive operation and it doesn't affect the results.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.