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