noiftimer.noiftimer

  1import time
  2from functools import wraps
  3from typing import Any, Callable
  4
  5from typing_extensions import Self
  6
  7
  8def time_it(loops: int = 1) -> Callable[..., Any]:
  9    """Decorator to time function execution time and print the results.
 10
 11    #### :params:
 12
 13    `loops`: How many times to execute the decorated function,
 14    starting and stopping the timer before and after each loop."""
 15
 16    def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
 17        @wraps(func)
 18        def wrapper(*args, **kwargs) -> Any:
 19            timer = Timer(loops)
 20            result = None
 21            for _ in range(loops):
 22                timer.start()
 23                result = func(*args, **kwargs)
 24                timer.stop()
 25            print(
 26                f"{func.__name__} {'average ' if loops > 1 else ''}execution time: {timer.average_elapsed_str}"
 27            )
 28            return result
 29
 30        return wrapper
 31
 32    return decorator
 33
 34
 35class _Pauser:
 36    def __init__(self):
 37        self._pause_start = 0
 38        self._pause_total = 0
 39        self._paused = False
 40
 41    def pause(self):
 42        self._pause_start = time.time()
 43        self._paused = True
 44
 45    def unpause(self):
 46        self._pause_total += time.time() - self._pause_start
 47        self._paused = False
 48
 49    def reset(self):
 50        self._pause_start = 0
 51        self._pause_total = 0
 52        self._paused = False
 53
 54    @property
 55    def pause_total(self) -> float:
 56        if self._paused:
 57            return self._pause_total + (time.time() - self._pause_start)
 58        else:
 59            return self._pause_total
 60
 61
 62class Timer:
 63    """Simple timer class that tracks total elapsed time
 64    and average time between calls to `start()` and `stop()`."""
 65
 66    def __init__(
 67        self, averaging_window_length: int = 10, subsecond_resolution: bool = True
 68    ):
 69        """
 70        #### :params:
 71        * `averaging_window_length`: Number of start/stop cycles to calculate the average elapsed time with.
 72
 73        * `subsecond_resolution`: Whether to print formatted time strings with subsecond resolution or not."""
 74        self._start_time = time.time()
 75        self._stop_time = self.start_time
 76        self._elapsed = 0
 77        self._average_elapsed = 0
 78        self._history: list[float] = []
 79        self._started: bool = False
 80        self.averaging_window_length: int = averaging_window_length
 81        self.subsecond_resolution = subsecond_resolution
 82        self._pauser = _Pauser()
 83
 84    @property
 85    def started(self) -> bool:
 86        """Returns whether the timer has been started and is currently running."""
 87        return self._started
 88
 89    @property
 90    def elapsed(self) -> float:
 91        """Returns the currently elapsed time."""
 92        if self._started:
 93            return time.time() - self._start_time - self._pauser.pause_total
 94        else:
 95            return self._elapsed
 96
 97    @property
 98    def elapsed_str(self) -> str:
 99        """Returns the currently elapsed time as a formatted string."""
100        return self.format_time(self.elapsed, self.subsecond_resolution)
101
102    @property
103    def average_elapsed(self) -> float:
104        """Returns the average elapsed time."""
105        return self._average_elapsed
106
107    @property
108    def average_elapsed_str(self) -> str:
109        """Returns the average elapsed time as a formatted string."""
110        return self.format_time(self._average_elapsed, self.subsecond_resolution)
111
112    @property
113    def start_time(self) -> float:
114        """Returns the timestamp of the last call to `start()`."""
115        return self._start_time
116
117    @property
118    def stop_time(self) -> float:
119        """Returns the timestamp of the last call to `stop()`."""
120        return self._stop_time
121
122    @property
123    def history(self) -> list[float]:
124        """Returns the history buffer for this timer.
125
126        At most, it will be `averaging_window_length` elements long."""
127        return self._history
128
129    def start(self: Self) -> Self:
130        """Start the timer.
131
132        Returns this Timer instance so timer start can be chained to Timer creation if desired.
133
134        >>> timer = Timer().start()"""
135        if not self.started:
136            self._start_time = time.time()
137            self._started = True
138        return self
139
140    def stop(self):
141        """Stop the timer.
142
143        Calculates elapsed time and average elapsed time."""
144        if self.started:
145            self._stop_time = time.time()
146            self._started = False
147            self._elapsed = (
148                self._stop_time - self._start_time - self._pauser.pause_total
149            )
150            self._pauser.reset()
151            self._save_elapsed_time()
152            self._average_elapsed = sum(self._history) / (len(self._history))
153
154    def pause(self):
155        """Pause the timer."""
156        self._pauser.pause()
157
158    def unpause(self):
159        """Unpause the timer."""
160        self._pauser.unpause()
161
162    def _save_elapsed_time(self):
163        """Saves current elapsed time to the history buffer in a FIFO manner."""
164        if len(self._history) >= self.averaging_window_length:
165            self._history.pop(0)
166        self._history.append(self._elapsed)
167
168    @staticmethod
169    def format_time(num_seconds: float, subsecond_resolution: bool = False) -> str:
170        """Returns `num_seconds` as a string with units.
171
172        #### :params:
173
174        `subsecond_resolution`: Include milliseconds and microseconds with the output."""
175        microsecond = 0.000001
176        millisecond = 0.001
177        second = 1
178        seconds_per_minute = 60
179        seconds_per_hour = 3600
180        seconds_per_day = 86400
181        seconds_per_week = 604800
182        seconds_per_month = 2419200
183        seconds_per_year = 29030400
184        time_units = [
185            (seconds_per_year, "y"),
186            (seconds_per_month, "mn"),
187            (seconds_per_week, "w"),
188            (seconds_per_day, "d"),
189            (seconds_per_hour, "h"),
190            (seconds_per_minute, "m"),
191            (second, "s"),
192            (millisecond, "ms"),
193            (microsecond, "us"),
194        ]
195        if not subsecond_resolution:
196            time_units = time_units[:-2]
197        time_string = ""
198        for time_unit in time_units:
199            unit_amount, num_seconds = divmod(num_seconds, time_unit[0])
200            if unit_amount > 0:
201                time_string += f"{int(unit_amount)}{time_unit[1]} "
202        if time_string == "":
203            return f"<1{time_units[-1][1]}"
204        return time_string.strip()
205
206    @property
207    def stats(self) -> str:
208        """Returns a string stating the currently elapsed time and the average elapsed time."""
209        return f"elapsed time: {self.elapsed_str}\naverage elapsed time: {self.average_elapsed_str}"
def time_it(loops: int = 1) -> Callable[..., Any]:
 9def time_it(loops: int = 1) -> Callable[..., Any]:
10    """Decorator to time function execution time and print the results.
11
12    #### :params:
13
14    `loops`: How many times to execute the decorated function,
15    starting and stopping the timer before and after each loop."""
16
17    def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
18        @wraps(func)
19        def wrapper(*args, **kwargs) -> Any:
20            timer = Timer(loops)
21            result = None
22            for _ in range(loops):
23                timer.start()
24                result = func(*args, **kwargs)
25                timer.stop()
26            print(
27                f"{func.__name__} {'average ' if loops > 1 else ''}execution time: {timer.average_elapsed_str}"
28            )
29            return result
30
31        return wrapper
32
33    return decorator

Decorator to time function execution time and print the results.

:params:

loops: How many times to execute the decorated function, starting and stopping the timer before and after each loop.

class Timer:
 63class Timer:
 64    """Simple timer class that tracks total elapsed time
 65    and average time between calls to `start()` and `stop()`."""
 66
 67    def __init__(
 68        self, averaging_window_length: int = 10, subsecond_resolution: bool = True
 69    ):
 70        """
 71        #### :params:
 72        * `averaging_window_length`: Number of start/stop cycles to calculate the average elapsed time with.
 73
 74        * `subsecond_resolution`: Whether to print formatted time strings with subsecond resolution or not."""
 75        self._start_time = time.time()
 76        self._stop_time = self.start_time
 77        self._elapsed = 0
 78        self._average_elapsed = 0
 79        self._history: list[float] = []
 80        self._started: bool = False
 81        self.averaging_window_length: int = averaging_window_length
 82        self.subsecond_resolution = subsecond_resolution
 83        self._pauser = _Pauser()
 84
 85    @property
 86    def started(self) -> bool:
 87        """Returns whether the timer has been started and is currently running."""
 88        return self._started
 89
 90    @property
 91    def elapsed(self) -> float:
 92        """Returns the currently elapsed time."""
 93        if self._started:
 94            return time.time() - self._start_time - self._pauser.pause_total
 95        else:
 96            return self._elapsed
 97
 98    @property
 99    def elapsed_str(self) -> str:
100        """Returns the currently elapsed time as a formatted string."""
101        return self.format_time(self.elapsed, self.subsecond_resolution)
102
103    @property
104    def average_elapsed(self) -> float:
105        """Returns the average elapsed time."""
106        return self._average_elapsed
107
108    @property
109    def average_elapsed_str(self) -> str:
110        """Returns the average elapsed time as a formatted string."""
111        return self.format_time(self._average_elapsed, self.subsecond_resolution)
112
113    @property
114    def start_time(self) -> float:
115        """Returns the timestamp of the last call to `start()`."""
116        return self._start_time
117
118    @property
119    def stop_time(self) -> float:
120        """Returns the timestamp of the last call to `stop()`."""
121        return self._stop_time
122
123    @property
124    def history(self) -> list[float]:
125        """Returns the history buffer for this timer.
126
127        At most, it will be `averaging_window_length` elements long."""
128        return self._history
129
130    def start(self: Self) -> Self:
131        """Start the timer.
132
133        Returns this Timer instance so timer start can be chained to Timer creation if desired.
134
135        >>> timer = Timer().start()"""
136        if not self.started:
137            self._start_time = time.time()
138            self._started = True
139        return self
140
141    def stop(self):
142        """Stop the timer.
143
144        Calculates elapsed time and average elapsed time."""
145        if self.started:
146            self._stop_time = time.time()
147            self._started = False
148            self._elapsed = (
149                self._stop_time - self._start_time - self._pauser.pause_total
150            )
151            self._pauser.reset()
152            self._save_elapsed_time()
153            self._average_elapsed = sum(self._history) / (len(self._history))
154
155    def pause(self):
156        """Pause the timer."""
157        self._pauser.pause()
158
159    def unpause(self):
160        """Unpause the timer."""
161        self._pauser.unpause()
162
163    def _save_elapsed_time(self):
164        """Saves current elapsed time to the history buffer in a FIFO manner."""
165        if len(self._history) >= self.averaging_window_length:
166            self._history.pop(0)
167        self._history.append(self._elapsed)
168
169    @staticmethod
170    def format_time(num_seconds: float, subsecond_resolution: bool = False) -> str:
171        """Returns `num_seconds` as a string with units.
172
173        #### :params:
174
175        `subsecond_resolution`: Include milliseconds and microseconds with the output."""
176        microsecond = 0.000001
177        millisecond = 0.001
178        second = 1
179        seconds_per_minute = 60
180        seconds_per_hour = 3600
181        seconds_per_day = 86400
182        seconds_per_week = 604800
183        seconds_per_month = 2419200
184        seconds_per_year = 29030400
185        time_units = [
186            (seconds_per_year, "y"),
187            (seconds_per_month, "mn"),
188            (seconds_per_week, "w"),
189            (seconds_per_day, "d"),
190            (seconds_per_hour, "h"),
191            (seconds_per_minute, "m"),
192            (second, "s"),
193            (millisecond, "ms"),
194            (microsecond, "us"),
195        ]
196        if not subsecond_resolution:
197            time_units = time_units[:-2]
198        time_string = ""
199        for time_unit in time_units:
200            unit_amount, num_seconds = divmod(num_seconds, time_unit[0])
201            if unit_amount > 0:
202                time_string += f"{int(unit_amount)}{time_unit[1]} "
203        if time_string == "":
204            return f"<1{time_units[-1][1]}"
205        return time_string.strip()
206
207    @property
208    def stats(self) -> str:
209        """Returns a string stating the currently elapsed time and the average elapsed time."""
210        return f"elapsed time: {self.elapsed_str}\naverage elapsed time: {self.average_elapsed_str}"

Simple timer class that tracks total elapsed time and average time between calls to start() and stop().

Timer(averaging_window_length: int = 10, subsecond_resolution: bool = True)
67    def __init__(
68        self, averaging_window_length: int = 10, subsecond_resolution: bool = True
69    ):
70        """
71        #### :params:
72        * `averaging_window_length`: Number of start/stop cycles to calculate the average elapsed time with.
73
74        * `subsecond_resolution`: Whether to print formatted time strings with subsecond resolution or not."""
75        self._start_time = time.time()
76        self._stop_time = self.start_time
77        self._elapsed = 0
78        self._average_elapsed = 0
79        self._history: list[float] = []
80        self._started: bool = False
81        self.averaging_window_length: int = averaging_window_length
82        self.subsecond_resolution = subsecond_resolution
83        self._pauser = _Pauser()

:params:

  • averaging_window_length: Number of start/stop cycles to calculate the average elapsed time with.

  • subsecond_resolution: Whether to print formatted time strings with subsecond resolution or not.

started: bool

Returns whether the timer has been started and is currently running.

elapsed: float

Returns the currently elapsed time.

elapsed_str: str

Returns the currently elapsed time as a formatted string.

average_elapsed: float

Returns the average elapsed time.

average_elapsed_str: str

Returns the average elapsed time as a formatted string.

start_time: float

Returns the timestamp of the last call to start().

stop_time: float

Returns the timestamp of the last call to stop().

history: list[float]

Returns the history buffer for this timer.

At most, it will be averaging_window_length elements long.

def start(self: Self) -> Self:
130    def start(self: Self) -> Self:
131        """Start the timer.
132
133        Returns this Timer instance so timer start can be chained to Timer creation if desired.
134
135        >>> timer = Timer().start()"""
136        if not self.started:
137            self._start_time = time.time()
138            self._started = True
139        return self

Start the timer.

Returns this Timer instance so timer start can be chained to Timer creation if desired.

>>> timer = Timer().start()
def stop(self):
141    def stop(self):
142        """Stop the timer.
143
144        Calculates elapsed time and average elapsed time."""
145        if self.started:
146            self._stop_time = time.time()
147            self._started = False
148            self._elapsed = (
149                self._stop_time - self._start_time - self._pauser.pause_total
150            )
151            self._pauser.reset()
152            self._save_elapsed_time()
153            self._average_elapsed = sum(self._history) / (len(self._history))

Stop the timer.

Calculates elapsed time and average elapsed time.

def pause(self):
155    def pause(self):
156        """Pause the timer."""
157        self._pauser.pause()

Pause the timer.

def unpause(self):
159    def unpause(self):
160        """Unpause the timer."""
161        self._pauser.unpause()

Unpause the timer.

@staticmethod
def format_time(num_seconds: float, subsecond_resolution: bool = False) -> str:
169    @staticmethod
170    def format_time(num_seconds: float, subsecond_resolution: bool = False) -> str:
171        """Returns `num_seconds` as a string with units.
172
173        #### :params:
174
175        `subsecond_resolution`: Include milliseconds and microseconds with the output."""
176        microsecond = 0.000001
177        millisecond = 0.001
178        second = 1
179        seconds_per_minute = 60
180        seconds_per_hour = 3600
181        seconds_per_day = 86400
182        seconds_per_week = 604800
183        seconds_per_month = 2419200
184        seconds_per_year = 29030400
185        time_units = [
186            (seconds_per_year, "y"),
187            (seconds_per_month, "mn"),
188            (seconds_per_week, "w"),
189            (seconds_per_day, "d"),
190            (seconds_per_hour, "h"),
191            (seconds_per_minute, "m"),
192            (second, "s"),
193            (millisecond, "ms"),
194            (microsecond, "us"),
195        ]
196        if not subsecond_resolution:
197            time_units = time_units[:-2]
198        time_string = ""
199        for time_unit in time_units:
200            unit_amount, num_seconds = divmod(num_seconds, time_unit[0])
201            if unit_amount > 0:
202                time_string += f"{int(unit_amount)}{time_unit[1]} "
203        if time_string == "":
204            return f"<1{time_units[-1][1]}"
205        return time_string.strip()

Returns num_seconds as a string with units.

:params:

subsecond_resolution: Include milliseconds and microseconds with the output.

stats: str

Returns a string stating the currently elapsed time and the average elapsed time.