sksundae.utils#

General-purpose module for shared utilities across the package.

Classes#

RichResult

Output container with pretty printing.

Timeout

Timeout context manager.

Timer

Timer context manager.

Package Contents#

class sksundae.utils.RichResult(**kwargs)[source]#

Output container with pretty printing.

A container class based off the _RichResult class in the scipy library. It combines a series of formatting functions to make the printed ‘repr’ easy to read. Use this class directly by passing in any number of keyword arguments, or use it as a base class to have custom classes for different result types.

Inheriting classes should define the class attribute _order_keys which is a list of strings that defines how output fields are sorted when an instance is printed.

Parameters:

**kwargs (dict, optional) – User-specified keyword arguments. Any number of arguments can be given as input. The class simply stores the key/value pairs, makes them accessible as attributes, and provides pretty printing.

Examples

The example below demonstrates how to define the _order_keys class attribute for custom sorting. If arguments are not in the list, they are placed at the end based on the order they were given. Note that _order_keys only provides sorting support and that no errors are raised if an argument is not present, e.g., third below.

class CustomResult(RichResult):
    _order_keys = ['first', 'second', 'third',]

result = CustomResult(second=None, last=None, first=None)
print(result)

RichResult can also be used directly, without any custom sorting. Arguments will print based on the order they were input. Instances will still have a fully formatted ‘repr’, including formatted arrays.

import numpy as np

t = np.linspace(0, 1, 1000)
y = np.random.rand(1000, 5)

y[0] = np.inf
y[-1] = np.nan

result = RichResult(message='Example.', status=0, t=t, y=y)
print(result)

After initialization, all key/value pairs are accessible as instance attributes.

result = RichResult(a=10, b=20, c=30)

print(result.a*(result.b + result.c))
class sksundae.utils.Timeout(seconds, name='Timeout', raise_exc=True)[source]#

Timeout context manager.

Uses a thread to track the time spent in a context block and forces an exit (and optionally raises a TimeoutError) if the execution time exceeds a given number of seconds. See the notes for important details on edge cases where this may not work as expected.

Parameters:
  • seconds (float) – Number of seconds before timing out.

  • name (str, optional) – Name to identify the block in exit messages, by default ‘Timeout’.

  • raise_exc (bool, optional) – If True, raise a TimeoutError on exit when the given time limit is exceeded. Use False to handle the timeout manually.

Notes

The KeyboardInterrupt that is used to force the exit of the main thread may not be immediate, depending on the state of the main thread. For example, if you are running time.sleep, the interruption will not be raised until the sleep is complete.

If you are using extensions that are not written in pure Python, the interruption may not be raised at all, depending on how that extension was written to catch and handle exceptions.

Examples

The Timeout class is used as a context manager, as shown below. Here, we force a timeout by setting the time limit to 1/1000 of the execution time to complete the slow_fibonacci function.

def slow_fibonacci(n):
    if n < 2:
        return n
    return slow_fibonacci(n-1) + slow_fibonacci(n-2)

with Timer() as timer:
    result = slow_fibonacci(30)

exec_time = timer.elapsed_time
time_limit = exec_time / 1000

with Timeout(time_limit) as timeout:
    result = slow_fibonacci(30)

By default, the context manager will raise an exception if the context block exceeds the set time limit. If you want to suppress this behavior, and handle it manually you can do so using raise_exc=False.

with Timeout(time_limit, raise_exc=False) as timeout:
    result = slow_fibonacci(30)

if timeout.expired:
    raise timeout.exception

Since we use a KeyboardInterrupt to stop the main thread, you may also want to differentiate between user-raised interrupts and timeouts. This also requires setting raise_exc=False to prevent an exception on exit.

with Timeout(time_limit, raise_exc=False) as timeout:

    try:
        result = slow_fibonacci(30)
    except KeyboardInterrupt:
        if timeout.expired:
            print('Timeout expired.')
        else:
            print('User interrupt.')  # or raise to stop execution

While demonstrated with a standin function for the Fibonacci sequence, this context manager can be used with any block and can help catch and cancel long-running operations. Note that the Timer and Timeout utilities can also be used in the same context block, as demonstrated below.

with Timer() as timer, Timeout(1000*exec_time) as timeout:
    result = slow_fibonacci(30)

print(f"{timeout.expired=}")  # should be False since limit is high
timer.print_elapsed()
property exception: TimeoutError#

The exception to raise if the timer expired.

property expired: bool#

Whether the timer has expired.

class sksundae.utils.Timer(name='Elapsed time', units='s', display=True)[source]#

Timer context manager.

Records and prints the elapsed time between entering and exiting a context block.

Parameters:
  • name (str, optional) – Context block name used in print. The default is ‘Elapsed time’.

  • units (Literal['s', 'min', 'h'], optional) – Units to use when printing the elapsed time. The default is ‘s’.

  • display (bool, optional) – If True, print on exit. Otherwise, only stores the elapsed time.

Notes

If you want to print in additional units, you can convert and print the elapsed time using the elapsed_time property.

A timer instance can be reused across multiple context blocks; however, the elapsed_time property will only store values from the last block.

Examples

The Timer works as a context manager:

import time

def function(sleep_time: float) -> None:
    time.sleep(sleep_time)

with Timer():
    function(2.)

If you want to silence printing, set display=False. You can then call print_elapsed at a later time, or access the elapsed_time property for custom printing:

with Timer(display=False) as timer:
    function(2.)

# print in all allowed units
for units in ['s', 'min', 'h']:
    timer.print_elapsed(units)

# custom printing, with higher precision
print(f"Elapsed time: {timer.elapsed_time:.10f} s")
print_elapsed(units='s')[source]#

Print the elapsed time.

Parameters:

units (Literal['s', 'min', 'h'], optional) – Units to use when printing the elapsed time. The default is ‘s’.

property elapsed_time: float#

The elapsed time in seconds.