Source code for devhelpers.timeit

# devhelpers - A Development Toolbox
# Copyright (c) 2021  Michael Sasser <Michael@MichaelSasser.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
from functools import wraps
from itertools import repeat
from sys import float_info
from time import time
from typing import Any
from typing import Callable
from typing import Dict
from typing import List
from typing import TypeVar
from typing import Union


__author__: str = "Michael Sasser"
__email__: str = "Michael@MichaelSasser.org"


ReturnType = TypeVar("ReturnType")


[docs]def timeit( arg: Union[Callable[..., ReturnType], int] ) -> Callable[..., ReturnType]: """Use the decorator to time functions in place. One hazard: When you use the decorator with a function/method that does change something outside it's namespace or a method changes anything inside the internal dict, and you let it repeat stuff, might result in an unexpected behavior. Notes ----- It is safe: - when the function/method does not e.g. count, append something outside its own namespace, when using the decorator with repeating enabled like ``@timeit(100)``. Or in other words: if the internal state does not change, when you call it $ n+1 $ times. - if you don't repeat anything: ``@timeit``. Examples -------- .. code-block:: python @timeit(100) def foo(a, b): pass @timeit def bar(c, d): pass :param arg: the callable or number of runs """ def timeit_(func: Callable[..., ReturnType]) -> Callable[..., ReturnType]: @wraps(func) def wrapper(*args: List[Any], **kwargs: Dict[str, Any]) -> Any: t_total: float = 0.0 max_: float = 0.0 min_: float = float_info.max # Runner for _ in repeat(None, n): t_start: float = time() result: Any = func(*args, **kwargs) t_stop: float = time() dt: float = t_stop - t_start min_ = min(dt, min_) max_ = max(dt, max_) t_total += dt if n > 1: print( f"TimeIt: {func.__qualname__}({args=}, {kwargs=}) ran " f"{n} times and took: total = {t_total:4.6E} s, min " f"= {min_:4.6E} s, max = {max_:4.6E} s, avg = " f"{t_total/n:4.6E} s" ) else: print( f"TimeIt: {func.__qualname__}({args=}, {kwargs=}) took " f"{t_total:4.6E} s" ) return result return wrapper # arg can be func, if no n provided. -> @timeit # arg can be n, if n provided. -> @timeit(100) n = 1 if isinstance(arg, int): n = arg return timeit_ # type: ignore return timeit_(arg)
# vim: set ft=python :