Coverage for muutils/timeit_fancy.py: 100%
31 statements
« prev ^ index » next coverage.py v7.6.1, created at 2025-04-04 03:33 -0600
« prev ^ index » next coverage.py v7.6.1, created at 2025-04-04 03:33 -0600
1"`timeit_fancy` is just a fancier version of timeit with more options"
3from __future__ import annotations
5import pstats
6import timeit
7import cProfile
8from typing import Callable, Union, TypeVar, NamedTuple, Any
9import warnings
11from muutils.statcounter import StatCounter
13T = TypeVar("T")
16class FancyTimeitResult(NamedTuple):
17 """return type of `timeit_fancy`"""
19 timings: StatCounter
20 return_value: T # type: ignore[valid-type]
21 profile: Union[pstats.Stats, None]
24def timeit_fancy(
25 cmd: Union[Callable[[], T], str],
26 setup: Union[str, Callable[[], Any]] = lambda: None,
27 repeats: int = 5,
28 namespace: Union[dict[str, Any], None] = None,
29 get_return: bool = True,
30 do_profiling: bool = False,
31) -> FancyTimeitResult:
32 """
33 Wrapper for `timeit` to get the fastest run of a callable with more customization options.
35 Approximates the functionality of the %timeit magic or command line interface in a Python callable.
37 # Parameters
39 - `cmd: Callable[[], T] | str`
40 The callable to time. If a string, it will be passed to `timeit.Timer` as the `stmt` argument.
41 - `setup: str`
42 The setup code to run before `cmd`. If a string, it will be passed to `timeit.Timer` as the `setup` argument.
43 - `repeats: int`
44 The number of times to run `cmd` to get a reliable measurement.
45 - `namespace: dict[str, Any]`
46 Passed to `timeit.Timer` constructor.
47 If `cmd` or `setup` use local or global variables, they must be passed here. See `timeit` documentation for details.
48 - `get_return: bool`
49 Whether to pass the value returned from `cmd`. If True, the return value will be appended in a tuple with execution time.
50 This is for speed and convenience so that `cmd` doesn't need to be run again in the calling scope if the return values are needed.
51 (default: `False`)
52 - `do_profiling: bool`
53 Whether to return a `pstats.Stats` object in addition to the time and return value.
54 (default: `False`)
56 # Returns
58 `FancyTimeitResult`, which is a NamedTuple with the following fields:
60 - `time: float`
61 The time in seconds it took to run `cmd` the minimum number of times to get a reliable measurement.
62 - `return_value: T|None`
63 The return value of `cmd` if `get_return` is `True`, otherwise `None`.
64 - `profile: pstats.Stats|None`
65 A `pstats.Stats` object if `do_profiling` is `True`, otherwise `None`.
66 """
67 timer: timeit.Timer = timeit.Timer(cmd, setup, globals=namespace)
69 # Perform the timing
70 times: list[float] = timer.repeat(repeats, 1)
72 # Optionally capture the return value
73 profile: pstats.Stats | None = None
75 return_value: T | None = None
76 if get_return or do_profiling:
77 # Optionally perform profiling
78 if do_profiling:
79 profiler = cProfile.Profile()
80 profiler.enable()
82 try:
83 return_value = cmd() # type: ignore[operator]
84 except TypeError as e:
85 warnings.warn(
86 f"Failed to get return value from `cmd` due to error (probably passing a string). will return `return_value=None`\n{e}",
87 )
89 if do_profiling:
90 profiler.disable()
91 profile = pstats.Stats(profiler).strip_dirs().sort_stats("cumulative")
93 # reset the return value if it wasn't requested
94 if not get_return:
95 return_value = None
97 return FancyTimeitResult(
98 timings=StatCounter(times),
99 return_value=return_value,
100 profile=profile,
101 )