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}"
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.
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()
.
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.
Returns the history buffer for this timer.
At most, it will be averaging_window_length
elements long.
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()
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.
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.