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