Path: blob/main/models_evaluation/timer/timer.py
1232 views
"""1This module aims to provide tools to easily time snippets of codes with color coding.2"""34import math5from datetime import datetime as dt6from time import time7from typing import Any89try:10from colorama import Fore, Style, init1112init()13except ModuleNotFoundError:14# Emulate the Fore and Style class of colorama with a class that as an empty string for every attributes.15class EmptyStringAttrClass:16def __getattr__(self, attr):17return ""1819Fore = EmptyStringAttrClass()20Style = EmptyStringAttrClass()2122__author__ = "Jean-Samuel Leboeuf, Frédérik Paradis"23__date__ = "May 28th, 2019"242526class Timer:27"""28This class can be used to time snippets of code inside your code. It prints colored information in the terminal.2930The class can be used as a context manager to time the code inside the 'with' statement, as a decorator of a31function or a method to time it at each call, or as an iterator to have the total running time of a32for loop as well as the mean time taken per iteration. See the documentation of the init method for usage examples.33"""3435def __init__(36self,37func_or_name=None,38*,39display_name="",40datetime_format="%Y-%m-%d %Hh%Mm%Ss",41elapsed_time_format="short",42main_color="LIGHTYELLOW_EX",43exception_exit_color="LIGHTRED_EX",44name_color="LIGHTBLUE_EX",45time_color="LIGHTCYAN_EX",46datetime_color="LIGHTMAGENTA_EX",47yield_timer=False,48):49"""50Args:51func_or_name (Union[Callable, str, None]):52If Timer is used as a decorator: If a callable, the callable will be wrapped and timed every time it is53called. If None, the callable will be set on the next call to Timer.54If Timer is used as a context manager: If a string, the string will be used as display name. If None,55no name will be displayed.56display_name (Union[str, None]):57String to be displayed to identify the timed snippet of code. Default (an empty string) will display58the name of the function. If set to None, will display nothing instead. Only useful if Timer59is used as a decorator, since the first arguments is used in the case of a context manager.60datetime_format (str or None, optional):61Datetime format used to display the date and time. The format follows the template of the 'datetime'62package. If None, no date or time will be displayed.63elapsed_time_format (either 'short' or 'long', optional):64Format used to display the elapsed time. If 'long', whole words will be used. If 'short', only the65first letters will be displayed.66main_color (str):67Color in which the main text will be displayed. Choices are those from the package colorama.68exception_exit_color (str):69Color in which the exception text will be displayed. Choices are those from the package colorama.70name_color (str):71Color in which the function name will be displayed. Choices are those from the package colorama.72time_color (str):73Color in which the time taken by the function will be displayed. Choices are those from the package74colorama.75datetime_color (str):76Color in which the date and time of day will be displayed. Choices are those from the package colorama.77yield_timer (bool):78Whether or not to yield the Timer object in addition to the output when Timer is used as an iterator.7980Supported colors:81BLACK, WHITE, RED, BLUE, GREEN, CYAN, MAGENTA, YELLOW, LIGHTRED_EX, BLIGHTLUE_EX, GRLIGHTEEN_EX,82CLIGHTYAN_EX, MAGELIGHTNTA_EX, YELLIGHTLOW_EX8384The class can be used as a context manager, a decorator or as an iterator.8586Examples as a context manager:87Example 1:88>>> from graal_utils import Timer89>>> with Timer():90... print("graal")91...92Execution started on 2019-05-09 13h48m23s.9394graal9596Execution completed in 0.00 seconds on 2019-05-09 13h48m23s.9798Example 2:99>>> from graal_utils import Timer100>>> with Timer("python", time_color="MAGENTA"):101... print("Python")102...103Execution of 'python' started on 2019-05-09 13h48m23s.104105Python106107Execution of 'python' completed in 0.00 seconds on 2019-05-09 13h48m23s.108109Examples as a decorator:110Example 1:111>>> from graal_utils import Timer112>>> @Timer113... def foo():114... print("foo!")115...116>>> foo()117Execution of 'foo' started on 2018-09-10 20h25m06s.118119foo!120121Execution of 'foo' completed in 0.00 seconds on 2018-09-10 20h25m06s.122123Example 2:124>>> @Timer(datetime_format='%Hh%Mm%Ss', display_func_name=False, main_color='WHITE')125... def bar():126... print("bar!")127... raise RuntimeError128...129>>> try:130... bar()131... except RuntimeError: pass132Execution started on 20h25m06s.133134bar!135136Execution terminated after 0.00 seconds on 20h25m06s.137138>>> bar.elapsed_time1390.5172324180603027140141Example 3:142>>> class Spam:143... @Timer144... def spam(self):145... print("egg!")146147>>> Spam().spam()148Execution of 'spam' started on 2018-10-02 18h33m14s.149150egg!151152Execution of 'spam' completed in 0.00 seconds on 2018-10-02 18h33m14s.153154Examples as an iterator:155Example 1: Simple case.156>>> for i in Timer(range(3)):157... time.sleep(.1)158... print(i)159Execution of 'range' started on 2021-04-23 15h09m30s.160161016211632164165Execution of 'range' completed in 0.33s on 2021-04-23 15h09m31s.166Mean time per iteration: 0.11s ± 0.00s over 3 iterations.167Iteration 0 was the shortest with 0.10s.168Iteration 1 was the longest with 0.11s.169170Example 2: Case with the Timer objected yielded.171>>> for i, t in Timer(range(3), yield_timer=True):172... sleep(.1)173... print(t.laps)174Execution of 'range' started on 2021-04-23 15h16m29s.175176177Execution of 'range' completed in 0.34s on 2021-04-23 15h16m29s.178Mean time per iteration: 0.11s ± 0.00s over 3 iterations.179Iteration 1 was the shortest with 0.11s.180Iteration 0 was the longest with 0.11s.181182[0.11200141906738281, 0.11099791526794434, 0.11100339889526367]183"""184if isinstance(func_or_name, str):185func, display_name = None, func_or_name186else:187func = func_or_name188189self._wrapped_func = self.wrap_function(func)190self.display_name = display_name191self.start_time = None192self.elapsed_time = None193self.datetime_format = datetime_format194self.elapsed_time_format = elapsed_time_format195196self.main_color = getattr(Fore, main_color)197self.exception_exit_color = getattr(Fore, exception_exit_color)198self.name_color = getattr(Fore, name_color)199self.time_color = getattr(Fore, time_color)200self.datetime_color = getattr(Fore, datetime_color)201202self.yield_timer = yield_timer203self.iter_stats = ""204205def __enter__(self):206self._start_timer()207return self208209def __exit__(self, exc_type, exc_value, traceback):210self.elapsed_time = time() - self.start_time211if exc_type:212self._exception_exit_end_timer()213else:214self._normal_exit_end_timer()215216@property217def func_name(self):218# pylint: disable=no-else-return219if self.display_name:220return f"of '{self.name_color}{self.display_name}{self.main_color}' "221elif self.display_name == "" and self._wrapped_func is not None:222return f"of '{self.name_color}{self.__name__}{self.main_color}' "223else:224return ""225226@property227def datetime(self):228# pylint: disable=no-else-return229if self.datetime_format is None:230return ""231else:232return "on " + self.datetime_color + dt.now().strftime(self.datetime_format) + self.main_color233234@staticmethod235def format_long_time(seconds, period):236# pylint: disable=no-else-return237periods = {"d": "day", "h": "hour", "m": "minute", "s": "second"}238239pluralize = lambda period_value: "s" if period_value > 1 else ""240format_period_string = periods[period] + pluralize(seconds)241if period != "s":242return f"{int(seconds)} {format_period_string}"243else:244return f"{seconds:.2f} {format_period_string}"245246@staticmethod247def format_short_time(seconds, period):248# pylint: disable=no-else-return249if period != "s":250return f"{int(seconds)}{period}"251else:252return f"{seconds:.2f}{period}"253254def format_elapsed_time(self, seconds):255is_long = self.elapsed_time_format == "long"256format_time = self.format_long_time if is_long else self.format_short_time257periods = {258"d": 60 * 60 * 24,259"h": 60 * 60,260"m": 60,261}262263time_strings = []264for period_name, period_seconds in periods.items():265if seconds >= period_seconds:266period_value, seconds = divmod(seconds, period_seconds)267time_strings.append(format_time(period_value, period_name))268269time_strings.append(format_time(seconds, "s"))270271return self.time_color + " ".join(time_strings) + self.main_color272273def _start_timer(self):274self.start_time = time()275print(self.main_color + f"Execution {self.func_name}started {self.datetime}.\n" + Style.RESET_ALL)276277def _exception_exit_end_timer(self):278print(279self.exception_exit_color280+ "\nExecution terminated after "281+ self.format_elapsed_time(self.elapsed_time)282+ f"{self.exception_exit_color} {self.datetime}{self.exception_exit_color}.\n{self.iter_stats}"283+ Style.RESET_ALL284)285286def _normal_exit_end_timer(self):287print(288self.main_color289+ f"\nExecution {self.func_name}completed in "290+ self.format_elapsed_time(self.elapsed_time)291+ f" {self.datetime}.\n{self.iter_stats}"292+ Style.RESET_ALL293)294295def wrap_function(self, func):296if func is not None:297self.__doc__ = func.__doc__298if hasattr(func, "__name__"):299self.__name__ = func.__name__300else:301self.__name__ = type(func).__name__ # For the case when Timer is used as an iterator.302303return func304305def __call__(self, *args: Any, **kwargs: Any) -> Any:306# pylint: disable=no-else-return307if self._wrapped_func is None:308self._wrapped_func = self.wrap_function(args[0])309return self310else:311with self:312return self._wrapped_func(*args, **kwargs)313314def __get__(self, parent_of_wrapped_method, type_of_parent_of_wrapped_method=None):315# Gets called only for wrapped methods. Sets the first argument of the function as the correct316# instance of 'self'.317self.__wrapped_method = self._wrapped_func318self._wrapped_func = lambda *args, **kwargs: self.__wrapped_method(parent_of_wrapped_method, *args, **kwargs)319return self320321def __iter__(self, *args, **kwargs):322with self:323self.laps = []324try:325for output in self._wrapped_func:326start_time = time()327yield (output, self) if self.yield_timer else output328self.laps.append(time() - start_time)329finally:330self._update_iter_stats()331332def _update_iter_stats(self):333mean_time = sum(self.laps) / len(self.laps)334std = math.sqrt(sum(t**2 for t in self.laps) / len(self.laps) - mean_time**2)335shortest_time = min((t, i) for i, t in enumerate(self.laps))336longest_time = max((t, i) for i, t in enumerate(self.laps))337self.iter_stats = (338self.main_color339+ f"Mean time per iteration: {self.format_elapsed_time(mean_time)} ± {self.format_elapsed_time(std)} over"340f" {len(self.laps)} iterations.\n"341+ f"Iteration {shortest_time[1]} was the shortest with {self.format_elapsed_time(shortest_time[0])}.\n"342+ f"Iteration {longest_time[1]} was the longest with {self.format_elapsed_time(longest_time[0])}.\n"343)344345346