Usage
The whole purpose of this micro library is to provide easy and quick access to measuring the time it takes to run a piece of code without populating the codebase with reoccurring and unnecessary variables. The library allows you to measure the run time of your code in two ways. Using decorators for callables and using context manager to measure run time of any code block using the with
statement.
The timer on it’s own does not do anything unless provided with triggers (see Triggers). In the following examples we will be using pytimers.timer
which is a provided instance of pytimers.Timer
containing single trigger that logs measured time to std output using standard logging library logging
using a trigger instance of pytimers.LoggerTrigger
.
Timer Decorator
The timer decorator can be applied to both synchronous and asynchronous functions and methods. PyTimers leverage python library decorator to make sure decorating will preserve the function/method signature, name and docstring.
Note
Decorating classes is currently not supported and will raise TypeError
.
import logging
from time import sleep
from pytimers import timer
logging.basicConfig(level=logging.INFO)
@timer
def func(*args: int):
print("Hello from func.")
sleep(1)
return sum(args)
if __name__ == "__main__":
func(1, 2, 3)
Hello from func.
INFO:pytimers.triggers.logger_trigger:Finished func in 1s 1.061ms [1.001s].
Class Methods and Static methods
To combine timer
decorator with decorators staticmethod()
and classmethod()
you have to first apply timer
decorator. Applying the decorators the other way around will result in TypeError
exception.
import logging
from time import sleep
from pytimers import timer
logging.basicConfig(level=logging.INFO)
class Foo:
@staticmethod
@timer
def method(*args: int):
print("Hello from static method.")
sleep(1)
return sum(args)
if __name__ == "__main__":
foo = Foo()
foo.method(1, 2, 3)
Hello from static method.
INFO:pytimers.triggers.logger_trigger:Finished Foo.method in 1s 1.025ms [1.001s].
Timer Context Manager
To measure time of any piece of code not enclosed in a callable object you can use timer
context manager capabilities.
import logging
from time import sleep
from pytimers import timer
logging.basicConfig(level=logging.INFO)
if __name__ == "__main__":
with timer:
print("Hello from code block.")
sleep(1)
Hello from code block.
INFO:pytimers.triggers.logger_trigger:Finished code block in 1s 1.143ms [1.001s].
Entering the context manager actually returns an instance of a pytimers.clock.Clock
. This allows you to access the current duration from inside of the code block but also the measured duration after the context manager is closed.
import logging
from time import sleep
from pytimers import timer
logging.basicConfig(level=logging.INFO)
if __name__ == "__main__":
with timer as t:
sleep(1)
print(f"We want to run this under 5s and so far it took {t.current_duration}.")
sleep(1)
print(f"We still had {5 - t.duration}s remaining.")
We want to run this under 5s and so far it took 1.0001475979988754.
INFO:pytimers.triggers.logger_trigger:Finished code block in 2s 1.384ms [2.001s].
We still had 2.998615708000216s remaining.
Block of code can also be named to increase log readability.
import logging
from time import sleep
from pytimers import timer
logging.basicConfig(level=logging.INFO)
if __name__ == "__main__":
with timer.label("data processing pipeline"):
print("Hello from code block.")
sleep(1)
Hello from code block.
INFO:pytimers.triggers.logger_trigger:Finished data processing pipeline in 1s 0.625ms [1.001s].
Timer context manager also allows you to stack context managers freely without a worry of interference.
import logging
from time import sleep
from pytimers import timer
logging.basicConfig(level=logging.INFO)
if __name__ == "__main__":
with timer.label("data collecting pipeline"):
print("Hello from code block n.1.")
sleep(1)
with timer:
print("Hello from code block n.2.")
sleep(1)
with timer.label("data processing pipeline"):
print("Hello from code block n.3.")
sleep(1)
Hello from code block n.1.
Hello from code block n.2.
Hello from code block n.3.
INFO:pytimers.triggers.logger_trigger:Finished data processing pipeline in 1s 1.207ms [1.001s].
INFO:pytimers.triggers.logger_trigger:Finished code block in 2s 2.895ms [2.003s].
INFO:pytimers.triggers.logger_trigger:Finished data collecting pipeline in 3s 4.176ms [3.004s].
Note
Timer context manager fully supports async code execution using contextvars.ContextVar
.
Triggers
Triggers are an abstraction for the action performed after each timer is finished. The simplest trigger can just log the measured time using standard logging
library. Trigger doing just that is already provided in the library as pytimers.LoggerTrigger
.
Triggers can be implemented in two ways. Either using a function with keywords arguments duration_s: float, decorator: bool, label: str
or by defining a pytimers.BaseTrigger
subclass.
The following two examples shows how to implement a trivial custom trigger using both methods.
Function Based Trigger
import logging
from time import sleep
from pytimers import Timer
def custom_trigger(duration_s: float, decorator: bool, label: str):
print(f"Measured duration is {duration_s}s.")
if __name__ == "__main__":
timer = Timer([custom_trigger])
with timer:
sleep(1)
Measured duration is 1.0010350150005252s.
BaseTrigger Subclass Trigger
import logging
from time import sleep
from typing import Optional
from pytimers import Timer, BaseTrigger
class CustomTrigger(BaseTrigger):
def __call__(
self,
duration_s: float,
decorator: bool,
label: Optional[str] = None,
) -> None:
print(f"Measured duration is {duration_s}s.")
if __name__ == "__main__":
timer = Timer([CustomTrigger()])
with timer:
sleep(1)
Measured duration is 1.0010350150005252s.