forked from samuelcolvin/python-devtools
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtimer.py
More file actions
99 lines (78 loc) · 2.97 KB
/
timer.py
File metadata and controls
99 lines (78 loc) · 2.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
from time import perf_counter
__all__ = ('Timer',)
MYPY = False
if MYPY:
from typing import Any, List, Optional
# required for type hinting because I (stupidly) added methods called `str`
StrType = str
class TimerResult:
def __init__(self, name: 'Optional[str]' = None, verbose: bool = True) -> None:
self._name = name
self.verbose = verbose
self.finish: 'Optional[float]' = None
self.start = perf_counter()
def capture(self) -> None:
self.finish = perf_counter()
def elapsed(self) -> float:
if self.finish:
return self.finish - self.start
else:
return -1
def str(self, dp: int = 3) -> StrType:
if self._name:
return f'{self._name}: {self.elapsed():0.{dp}f}s elapsed'
else:
return f'{self.elapsed():0.{dp}f}s elapsed'
def __str__(self) -> StrType:
return self.str()
_SUMMARY_TEMPLATE = '{count} times: mean={mean:0.{dp}f}s stdev={stddev:0.{dp}f}s min={min:0.{dp}f}s max={max:0.{dp}f}s'
class Timer:
def __init__(self, name: 'Optional[str]' = None, verbose: bool = True, file: 'Any' = None, dp: int = 3) -> None:
self.file = file
self.dp = dp
self._name = name
self._verbose = verbose
self.results: 'List[TimerResult]' = []
def __call__(self, name: 'Optional[str]' = None, verbose: 'Optional[bool]' = None) -> 'Timer':
if name:
self._name = name
if verbose is not None:
self._verbose = verbose
return self
def start(self, name: 'Optional[str]' = None, verbose: 'Optional[bool]' = None) -> 'Timer':
self.results.append(TimerResult(name or self._name, self._verbose if verbose is None else verbose))
return self
def capture(self, verbose: 'Optional[bool]' = None) -> 'TimerResult':
r = self.results[-1]
r.capture()
print_ = r.verbose if verbose is None else verbose
if print_:
print(r.str(self.dp), file=self.file, flush=True)
return r
def summary(self, verbose: bool = False) -> 'List[float]':
times = []
for r in self.results:
if not r.finish:
r.capture()
if verbose:
print(f' {r.str(self.dp)}', file=self.file)
times.append(r.elapsed())
if times:
from statistics import mean, stdev
print(
f'{len(times)} times: '
f'mean={mean(times):0.{self.dp}f}s '
f'stdev={stdev(times) if len(times) > 1 else 0:0.{self.dp}f}s '
f'min={min(times):0.{self.dp}f}s '
f'max={max(times):0.{self.dp}f}s',
file=self.file,
flush=True,
)
else:
raise RuntimeError('timer not started')
return times
def __enter__(self) -> 'Timer':
self.start()
return self
def __exit__(self, *args: 'Any') -> None:
self.capture()