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

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

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

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: collections.deque[float]

Returns the history buffer for this timer.

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

def reset(self):
158    def reset(self):
159        """Calls stop() then start() for convenience."""
160        self.stop()
161        self.start()

Calls stop() then start() for convenience.

def pause(self):
163    def pause(self):
164        """Pause the timer."""
165        self._pauser.pause()

Pause the timer.

def unpause(self):
167    def unpause(self):
168        """Unpause the timer."""
169        self._pauser.unpause()

Unpause the timer.

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

stats: str

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