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 ' if loops > 1 else ''}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}"
def time_it(loops: int = 1) -> Callable[..., Any]:
 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 ' if loops > 1 else ''}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.

class Timer:
 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().

Timer(averaging_window_length: int = 10, subsecond_resolution: bool = True)
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.

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:
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()
def stop(self):
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.

def pause(self):
153    def pause(self):
154        """Pause the timer."""
155        self._pauser.pause()

Pause the timer.

def unpause(self):
157    def unpause(self):
158        """Unpause the timer."""
159        self._pauser.unpause()

Unpause the timer.

@staticmethod
def format_time(num_seconds: float, subsecond_resolution: bool = False) -> str:
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.

stats: str

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