Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
GRAAL-Research
GitHub Repository: GRAAL-Research/deepparse
Path: blob/main/models_evaluation/timer/timer.py
1232 views
1
"""
2
This module aims to provide tools to easily time snippets of codes with color coding.
3
"""
4
5
import math
6
from datetime import datetime as dt
7
from time import time
8
from typing import Any
9
10
try:
11
from colorama import Fore, Style, init
12
13
init()
14
except ModuleNotFoundError:
15
# Emulate the Fore and Style class of colorama with a class that as an empty string for every attributes.
16
class EmptyStringAttrClass:
17
def __getattr__(self, attr):
18
return ""
19
20
Fore = EmptyStringAttrClass()
21
Style = EmptyStringAttrClass()
22
23
__author__ = "Jean-Samuel Leboeuf, Frédérik Paradis"
24
__date__ = "May 28th, 2019"
25
26
27
class Timer:
28
"""
29
This class can be used to time snippets of code inside your code. It prints colored information in the terminal.
30
31
The class can be used as a context manager to time the code inside the 'with' statement, as a decorator of a
32
function or a method to time it at each call, or as an iterator to have the total running time of a
33
for loop as well as the mean time taken per iteration. See the documentation of the init method for usage examples.
34
"""
35
36
def __init__(
37
self,
38
func_or_name=None,
39
*,
40
display_name="",
41
datetime_format="%Y-%m-%d %Hh%Mm%Ss",
42
elapsed_time_format="short",
43
main_color="LIGHTYELLOW_EX",
44
exception_exit_color="LIGHTRED_EX",
45
name_color="LIGHTBLUE_EX",
46
time_color="LIGHTCYAN_EX",
47
datetime_color="LIGHTMAGENTA_EX",
48
yield_timer=False,
49
):
50
"""
51
Args:
52
func_or_name (Union[Callable, str, None]):
53
If Timer is used as a decorator: If a callable, the callable will be wrapped and timed every time it is
54
called. If None, the callable will be set on the next call to Timer.
55
If Timer is used as a context manager: If a string, the string will be used as display name. If None,
56
no name will be displayed.
57
display_name (Union[str, None]):
58
String to be displayed to identify the timed snippet of code. Default (an empty string) will display
59
the name of the function. If set to None, will display nothing instead. Only useful if Timer
60
is used as a decorator, since the first arguments is used in the case of a context manager.
61
datetime_format (str or None, optional):
62
Datetime format used to display the date and time. The format follows the template of the 'datetime'
63
package. If None, no date or time will be displayed.
64
elapsed_time_format (either 'short' or 'long', optional):
65
Format used to display the elapsed time. If 'long', whole words will be used. If 'short', only the
66
first letters will be displayed.
67
main_color (str):
68
Color in which the main text will be displayed. Choices are those from the package colorama.
69
exception_exit_color (str):
70
Color in which the exception text will be displayed. Choices are those from the package colorama.
71
name_color (str):
72
Color in which the function name will be displayed. Choices are those from the package colorama.
73
time_color (str):
74
Color in which the time taken by the function will be displayed. Choices are those from the package
75
colorama.
76
datetime_color (str):
77
Color in which the date and time of day will be displayed. Choices are those from the package colorama.
78
yield_timer (bool):
79
Whether or not to yield the Timer object in addition to the output when Timer is used as an iterator.
80
81
Supported colors:
82
BLACK, WHITE, RED, BLUE, GREEN, CYAN, MAGENTA, YELLOW, LIGHTRED_EX, BLIGHTLUE_EX, GRLIGHTEEN_EX,
83
CLIGHTYAN_EX, MAGELIGHTNTA_EX, YELLIGHTLOW_EX
84
85
The class can be used as a context manager, a decorator or as an iterator.
86
87
Examples as a context manager:
88
Example 1:
89
>>> from graal_utils import Timer
90
>>> with Timer():
91
... print("graal")
92
...
93
Execution started on 2019-05-09 13h48m23s.
94
95
graal
96
97
Execution completed in 0.00 seconds on 2019-05-09 13h48m23s.
98
99
Example 2:
100
>>> from graal_utils import Timer
101
>>> with Timer("python", time_color="MAGENTA"):
102
... print("Python")
103
...
104
Execution of 'python' started on 2019-05-09 13h48m23s.
105
106
Python
107
108
Execution of 'python' completed in 0.00 seconds on 2019-05-09 13h48m23s.
109
110
Examples as a decorator:
111
Example 1:
112
>>> from graal_utils import Timer
113
>>> @Timer
114
... def foo():
115
... print("foo!")
116
...
117
>>> foo()
118
Execution of 'foo' started on 2018-09-10 20h25m06s.
119
120
foo!
121
122
Execution of 'foo' completed in 0.00 seconds on 2018-09-10 20h25m06s.
123
124
Example 2:
125
>>> @Timer(datetime_format='%Hh%Mm%Ss', display_func_name=False, main_color='WHITE')
126
... def bar():
127
... print("bar!")
128
... raise RuntimeError
129
...
130
>>> try:
131
... bar()
132
... except RuntimeError: pass
133
Execution started on 20h25m06s.
134
135
bar!
136
137
Execution terminated after 0.00 seconds on 20h25m06s.
138
139
>>> bar.elapsed_time
140
0.5172324180603027
141
142
Example 3:
143
>>> class Spam:
144
... @Timer
145
... def spam(self):
146
... print("egg!")
147
148
>>> Spam().spam()
149
Execution of 'spam' started on 2018-10-02 18h33m14s.
150
151
egg!
152
153
Execution of 'spam' completed in 0.00 seconds on 2018-10-02 18h33m14s.
154
155
Examples as an iterator:
156
Example 1: Simple case.
157
>>> for i in Timer(range(3)):
158
... time.sleep(.1)
159
... print(i)
160
Execution of 'range' started on 2021-04-23 15h09m30s.
161
162
0
163
1
164
2
165
166
Execution of 'range' completed in 0.33s on 2021-04-23 15h09m31s.
167
Mean time per iteration: 0.11s ± 0.00s over 3 iterations.
168
Iteration 0 was the shortest with 0.10s.
169
Iteration 1 was the longest with 0.11s.
170
171
Example 2: Case with the Timer objected yielded.
172
>>> for i, t in Timer(range(3), yield_timer=True):
173
... sleep(.1)
174
... print(t.laps)
175
Execution of 'range' started on 2021-04-23 15h16m29s.
176
177
178
Execution of 'range' completed in 0.34s on 2021-04-23 15h16m29s.
179
Mean time per iteration: 0.11s ± 0.00s over 3 iterations.
180
Iteration 1 was the shortest with 0.11s.
181
Iteration 0 was the longest with 0.11s.
182
183
[0.11200141906738281, 0.11099791526794434, 0.11100339889526367]
184
"""
185
if isinstance(func_or_name, str):
186
func, display_name = None, func_or_name
187
else:
188
func = func_or_name
189
190
self._wrapped_func = self.wrap_function(func)
191
self.display_name = display_name
192
self.start_time = None
193
self.elapsed_time = None
194
self.datetime_format = datetime_format
195
self.elapsed_time_format = elapsed_time_format
196
197
self.main_color = getattr(Fore, main_color)
198
self.exception_exit_color = getattr(Fore, exception_exit_color)
199
self.name_color = getattr(Fore, name_color)
200
self.time_color = getattr(Fore, time_color)
201
self.datetime_color = getattr(Fore, datetime_color)
202
203
self.yield_timer = yield_timer
204
self.iter_stats = ""
205
206
def __enter__(self):
207
self._start_timer()
208
return self
209
210
def __exit__(self, exc_type, exc_value, traceback):
211
self.elapsed_time = time() - self.start_time
212
if exc_type:
213
self._exception_exit_end_timer()
214
else:
215
self._normal_exit_end_timer()
216
217
@property
218
def func_name(self):
219
# pylint: disable=no-else-return
220
if self.display_name:
221
return f"of '{self.name_color}{self.display_name}{self.main_color}' "
222
elif self.display_name == "" and self._wrapped_func is not None:
223
return f"of '{self.name_color}{self.__name__}{self.main_color}' "
224
else:
225
return ""
226
227
@property
228
def datetime(self):
229
# pylint: disable=no-else-return
230
if self.datetime_format is None:
231
return ""
232
else:
233
return "on " + self.datetime_color + dt.now().strftime(self.datetime_format) + self.main_color
234
235
@staticmethod
236
def format_long_time(seconds, period):
237
# pylint: disable=no-else-return
238
periods = {"d": "day", "h": "hour", "m": "minute", "s": "second"}
239
240
pluralize = lambda period_value: "s" if period_value > 1 else ""
241
format_period_string = periods[period] + pluralize(seconds)
242
if period != "s":
243
return f"{int(seconds)} {format_period_string}"
244
else:
245
return f"{seconds:.2f} {format_period_string}"
246
247
@staticmethod
248
def format_short_time(seconds, period):
249
# pylint: disable=no-else-return
250
if period != "s":
251
return f"{int(seconds)}{period}"
252
else:
253
return f"{seconds:.2f}{period}"
254
255
def format_elapsed_time(self, seconds):
256
is_long = self.elapsed_time_format == "long"
257
format_time = self.format_long_time if is_long else self.format_short_time
258
periods = {
259
"d": 60 * 60 * 24,
260
"h": 60 * 60,
261
"m": 60,
262
}
263
264
time_strings = []
265
for period_name, period_seconds in periods.items():
266
if seconds >= period_seconds:
267
period_value, seconds = divmod(seconds, period_seconds)
268
time_strings.append(format_time(period_value, period_name))
269
270
time_strings.append(format_time(seconds, "s"))
271
272
return self.time_color + " ".join(time_strings) + self.main_color
273
274
def _start_timer(self):
275
self.start_time = time()
276
print(self.main_color + f"Execution {self.func_name}started {self.datetime}.\n" + Style.RESET_ALL)
277
278
def _exception_exit_end_timer(self):
279
print(
280
self.exception_exit_color
281
+ "\nExecution terminated after "
282
+ self.format_elapsed_time(self.elapsed_time)
283
+ f"{self.exception_exit_color} {self.datetime}{self.exception_exit_color}.\n{self.iter_stats}"
284
+ Style.RESET_ALL
285
)
286
287
def _normal_exit_end_timer(self):
288
print(
289
self.main_color
290
+ f"\nExecution {self.func_name}completed in "
291
+ self.format_elapsed_time(self.elapsed_time)
292
+ f" {self.datetime}.\n{self.iter_stats}"
293
+ Style.RESET_ALL
294
)
295
296
def wrap_function(self, func):
297
if func is not None:
298
self.__doc__ = func.__doc__
299
if hasattr(func, "__name__"):
300
self.__name__ = func.__name__
301
else:
302
self.__name__ = type(func).__name__ # For the case when Timer is used as an iterator.
303
304
return func
305
306
def __call__(self, *args: Any, **kwargs: Any) -> Any:
307
# pylint: disable=no-else-return
308
if self._wrapped_func is None:
309
self._wrapped_func = self.wrap_function(args[0])
310
return self
311
else:
312
with self:
313
return self._wrapped_func(*args, **kwargs)
314
315
def __get__(self, parent_of_wrapped_method, type_of_parent_of_wrapped_method=None):
316
# Gets called only for wrapped methods. Sets the first argument of the function as the correct
317
# instance of 'self'.
318
self.__wrapped_method = self._wrapped_func
319
self._wrapped_func = lambda *args, **kwargs: self.__wrapped_method(parent_of_wrapped_method, *args, **kwargs)
320
return self
321
322
def __iter__(self, *args, **kwargs):
323
with self:
324
self.laps = []
325
try:
326
for output in self._wrapped_func:
327
start_time = time()
328
yield (output, self) if self.yield_timer else output
329
self.laps.append(time() - start_time)
330
finally:
331
self._update_iter_stats()
332
333
def _update_iter_stats(self):
334
mean_time = sum(self.laps) / len(self.laps)
335
std = math.sqrt(sum(t**2 for t in self.laps) / len(self.laps) - mean_time**2)
336
shortest_time = min((t, i) for i, t in enumerate(self.laps))
337
longest_time = max((t, i) for i, t in enumerate(self.laps))
338
self.iter_stats = (
339
self.main_color
340
+ f"Mean time per iteration: {self.format_elapsed_time(mean_time)} ± {self.format_elapsed_time(std)} over"
341
f" {len(self.laps)} iterations.\n"
342
+ f"Iteration {shortest_time[1]} was the shortest with {self.format_elapsed_time(shortest_time[0])}.\n"
343
+ f"Iteration {longest_time[1]} was the longest with {self.format_elapsed_time(longest_time[0])}.\n"
344
)
345
346