Skip to content

04_Time

Thomas Byr edited this page May 28, 2026 · 2 revisions

Time utilities

  1. Measure the duration of a code block
    1. As a context manager
    2. Pass a function and its arguments
    3. Exhaust an iterator
  2. Force the throughput of a loop
    1. With an iterator
    2. Without an iterator
  3. Chaining (just an example)

The nob.time module provides utilities for measuring the duration of code blocks and for forcing the throughput of loops. These utilities are designed on top of the nob.human module.

Measure the duration of a code block

The time.about function behaves in 3 modes.

As a context manager

You can simply wrap your code block with time.about to measure its duration. Get the duration in seconds with t.duration or in a human-readable format with t.duration_human.

from time import sleep
from nob import time

with time.about() as t:
    sleep(1)
print(f"Elapsed time: {t.duration_human}.")

Pass a function and its arguments

You can also pass a function and its arguments to time.about to measure the duration of the function call. The result of the function call will be stored in t.result. The arguments can be passed as a mix of positional and keyword arguments.

from time import sleep
from nob import time

def my_func(x: int, y: int):
    sleep(1)
    return x + y

res = time.about(my_func, 1, y=2)
print(f"Result: {res.result}, Time taken: {res.duration_human}.")

Exhaust an iterator

If you pass an iterator to time.about, it will exhaust the iterator and measure the time taken to do so. The number of items exhausted will be stored in t.count, and a human-readable version of it in t.count_human. The throughput (items per second) will be stored in t.throughput, and a human-readable version of it in t.throughput_human.

from time import sleep
from nob import time

it = time.about(range(100))
for _ in it:
    sleep(0.01)
print(f"Exhausted {it.count_human} items in {it.duration_human} (at {it.throughput_human}).")

Force the throughput of a loop

The time.tick function allows you to force the throughput of a loop. It will automatically sleep for the right amount of time between items to achieve the desired throughput. The throughput should be specified in items per second. You can optionally tell the function to average over any period of time using the mean_over parameter (in seconds).

With an iterator

You can wrap your iterator with time.about.

from nob import time

it = time.about(range(100))
for _ in time.tick(it):
    pass
print(f"Exhausted {it.count_human} items in {it.duration_human} (at {it.throughput_human}).")

This code block should be a lot closer to 1 second than the previous example!

Without an iterator

The time.tick function can be called without an iterator as well. In this case, it will simply sleep for the right amount of time between calls to achieve the desired throughput.

from nob import time

it = time.about(range(100))
for _ in it:
    time.tick()
print(f"Exhausted {it.count_human} items in {it.duration_human} (at {it.throughput_human}).")

Both mode of operation should be equivalent.

However, not supplying an iterator to time.tick can be harmful since we have to rely on sys._getframe(1) to create new instances of the internal time.TickRateCounter, because we don't wan't multiple independent loops affecting each other's throughput. There are some edge cases which can cause this to break.

Tip

Pass your own time.TickRateCounter instances (if you know when to create new ones) using the safe_counter parameter to time.tick to avoid the potential issues. Call .reset() to clear all registered frames.

Chaining (just an example)

Because why not.

from nob import progress, time

it = time.about(time.tick(progress.track(range(100))))
for _ in it:
    pass
print(f"Exhausted {it.count_human} items in {it.duration_human} (at {it.throughput_human}).")

Clone this wiki locally