Skip to content

Overview

The exputils package has several submodules that are organized according to functionality. The submodules must not be imported individually. After importing the exputils module all can be accessed through it.

List of submodules:

  • exputils: Basic functionality that contain the AttrDict to define experiment configurations and functions that help to use the configuration.
  • exputils.manage: Functions to generate experiments from a configuration template and to execute them with support for parallel execution. See the Manage section for details.
  • exputils.data: Functions that are data related, such as Logging and Loading of experiment data.
  • exputils.gui.jupyter: Widgets and plotting functions to load and plot logged data in Jupyter notebook. See the Visualization section for details.
  • exputils.io: Basic IO helper functions which are used by the exputils package. They are usually not needed to log or load data which is done with the functions under the exputils.data module.
  • exputils.misc: Various helper functions used by the exputils package. Not yet documented.

Note: As this is a one-person development project, not all functionality is documented yet. In question, please refer directly to the source code or contact me.

Basic Functions

AttrDict

Bases: dict

A dictionary that provides attribute-style access. Can be used to configure experiments.

Example:

>>> b = AttrDict()
>>> b.hello = 'world'
>>> b.hello
'world'
>>> b['hello'] += "!"
>>> b.hello
'world!'
>>> b.foo = AttrDict(lol=True)
>>> b.foo.lol
True
>>> b.foo is b['foo']
True
A AttrDict is a subclass of dict; it supports all the methods a dict does...
>>> sorted(b.keys())
['foo', 'hello']
Including update()...
>>> b.update({ 'ponies': 'are pretty!' }, hello=42)
>>> print (repr(b))
AttrDict({'ponies': 'are pretty!', 'foo': Munch({'lol': True}), 'hello': 42})
As well as iteration...
>>> sorted([ (k,b[k]) for k in b ])
[('foo', AttrDict({'lol': True})), ('hello', 42), ('ponies', 'are pretty!')]
And "splats".
>>> "The {knights} who say {ni}!".format(**AttrDict(knights='lolcats', ni='can haz'))
'The lolcats who say can haz!'

Source code in exputils/misc/attrdict.py
class AttrDict(dict):
    """ A dictionary that provides attribute-style access. Can be used to configure experiments.

    Example:
    ```python
    >>> b = AttrDict()
    >>> b.hello = 'world'
    >>> b.hello
    'world'
    >>> b['hello'] += "!"
    >>> b.hello
    'world!'
    >>> b.foo = AttrDict(lol=True)
    >>> b.foo.lol
    True
    >>> b.foo is b['foo']
    True
    A AttrDict is a subclass of dict; it supports all the methods a dict does...
    >>> sorted(b.keys())
    ['foo', 'hello']
    Including update()...
    >>> b.update({ 'ponies': 'are pretty!' }, hello=42)
    >>> print (repr(b))
    AttrDict({'ponies': 'are pretty!', 'foo': Munch({'lol': True}), 'hello': 42})
    As well as iteration...
    >>> sorted([ (k,b[k]) for k in b ])
    [('foo', AttrDict({'lol': True})), ('hello', 42), ('ponies', 'are pretty!')]
    And "splats".
    >>> "The {knights} who say {ni}!".format(**AttrDict(knights='lolcats', ni='can haz'))
    'The lolcats who say can haz!'
    ```
    """

    # only called if k not found in normal places
    def __getattr__(self, k):
        """ Gets key if it exists, otherwise throws AttributeError.

        nb. __getattr__ is only called if key is not found in normal places.

        Example:
        ```python
            >>> b = AttrDict(bar='baz', lol={})
            >>> b.foo
            Traceback (most recent call last):
                ...
            AttributeError: foo
            >>> b.bar
            'baz'
            >>> getattr(b, 'bar')
            'baz'
            >>> b['bar']
            'baz'
            >>> b.lol is b['lol']
            True
            >>> b.lol is getattr(b, 'lol')
            True
        ```
        """
        try:
            # Throws exception if not in prototype chain
            return object.__getattribute__(self, k)
        except AttributeError:
            try:
                return self[k]
            except KeyError:
                raise AttributeError(k)


    def __setattr__(self, k, v):
        """ Sets attribute k if it exists, otherwise sets key k. A KeyError
            raised by set-item (only likely if you subclass Munch) will
            propagate as an AttributeError instead.

        Example:
        ```python
            >>> b = AttrDict(foo='bar', this_is='useful when subclassing')
            >>> hasattr(b.values, '__call__')
            True
            >>> b.values = 'uh oh'
            >>> b.values
            'uh oh'
            >>> b['values']
            Traceback (most recent call last):
                ...
            KeyError: 'values'
        ```
        """
        try:
            # Throws exception if not in prototype chain
            object.__getattribute__(self, k)
        except AttributeError:
            try:
                self[k] = v
            except:
                raise AttributeError(k)
        else:
            object.__setattr__(self, k, v)


    def __delattr__(self, k):
        """ Deletes attribute k if it exists, otherwise deletes key k. A KeyError
            raised by deleting the key--such as when the key is missing--will
            propagate as an AttributeError instead.

        Example:
        ```python
            >>> b = AttrDict(lol=42)
            >>> del b.lol
            >>> b.lol
            Traceback (most recent call last):
                ...
            AttributeError: lol
        ```
        """
        try:
            # Throws exception if not in prototype chain
            object.__getattribute__(self, k)
        except AttributeError:
            try:
                del self[k]
            except KeyError:
                raise AttributeError(k)
        else:
            object.__delattr__(self, k)


    def toDict(self):
        """ Recursively converts a munch back into a dictionary.

        Example:
        ```python
            >>> b = AttrDict(foo=AttrDict(lol=True), hello=42, ponies='are pretty!')
            >>> sorted(b.toDict().items())
            [('foo', {'lol': True}), ('hello', 42), ('ponies', 'are pretty!')]
            See unmunchify for more info.
        ```
        """
        return attrdict_to_dict(self)


    @property
    def __dict__(self):
        return self.toDict()


    def __repr__(self):
        """ Invertible string-form of a Munch.

        (Invertible so long as collection contents are each repr-invertible.)

        Example:
        ```python
            >>> b = AttrDict(foo=AttrDict(lol=True), hello=42, ponies='are pretty!')
            >>> print (repr(b))
            Munch({'ponies': 'are pretty!', 'foo': Munch({'lol': True}), 'hello': 42})
            >>> eval(repr(b))
            Munch({'ponies': 'are pretty!', 'foo': Munch({'lol': True}), 'hello': 42})
            >>> with_spaces = AttrDict({1: 2, 'a b': 9, 'c': AttrDict({'simple': 5})})
            >>> print (repr(with_spaces))
            Munch({'a b': 9, 1: 2, 'c': Munch({'simple': 5})})
            >>> eval(repr(with_spaces))
            Munch({'a b': 9, 1: 2, 'c': Munch({'simple': 5})})
        ```
        """
        return '{0}({1})'.format(self.__class__.__name__, dict.__repr__(self))


    def __dir__(self):
        return list(iterkeys(self))


    def __getstate__(self):
        """ Implement a serializable interface used for pickling.
        See https://docs.python.org/3.6/library/pickle.html.
        """
        return {k: v for k, v in self.items()}


    def __setstate__(self, state):
        """ Implement a serializable interface used for pickling.
        See https://docs.python.org/3.6/library/pickle.html.
        """
        self.clear()
        self.update(state)


    __members__ = __dir__  # for python2.x compatibility


    def __eq__(self, other):
        '''Is the dict equal to another dict. Allows to compare numpy arrays.'''
        return exputils.misc.dict_equal(self, other)


    @classmethod
    def from_dict(cls, d):
        """ Recursively transforms a dictionary into a AttrDict via copy.
            >>> b = AttrDict.from_dict({'urmom': {'sez': {'what': 'what'}}})
            >>> b.urmom.sez.what
            'what'
            See dict_to_attrdict for more info.
        """
        return dict_to_attrdict(d, cls)


    def copy(self):
        return type(self).from_dict(self)


    def to_json(self, **options):
        """ Serializes this AttrDict to JSON. Accepts the same keyword options as `json.dumps()`.
            >>> b = AttrDict(foo=AttrDict(lol=True), hello=42, ponies='are pretty!')
            >>> json.dumps(b) == b.to_json()
            True

            Allows to dump numpy arrays into JSON.
        """

        # allow to dump numpy into json
        if 'cls' not in options:
            options['cls'] = exputils.io.json.ExputilsJSONEncoder

        return json.dumps(self, **options)


    @classmethod
    def from_json(cls, json_data, is_transform_ints=True, **options):
        """ Loads an AttrDict from JSON. Accepts the same keyword options as `json.loads()`."""

        # allow to load numpy from json
        if 'cls' not in options:
            options['object_hook'] = exputils.io.json.exputils_json_object_hook

        loaded_json = json.loads(json_data, **options)

        # chenge possible int keys to ints as json changes ints to strings
        if is_transform_ints:
            loaded_json = exputils.io.convert_json_dict_keys_to_ints(loaded_json)

        return dict_to_attrdict(loaded_json, cls)


    def to_json_file(self, filepath, **options):
        exputils.io.save_dict_as_json_file(self, filepath, **options)


    @classmethod
    def from_json_file(cls, filepath, **options):
        loaded_dict = exputils.io.load_dict_from_json_file(filepath, **options)
        return dict_to_attrdict(loaded_dict, cls)


    def to_yaml(self, path) -> None:

        with open(path, 'w') as output_file:
            yaml.dump(self.toDict(), output_file)

    @classmethod
    def from_yaml(cls, filepath, **options):
        with open(filepath, 'r') as config_file:
            loaded_dict = yaml.load(config_file.read(),
                                    yaml.FullLoader)
        return dict_to_attrdict(loaded_dict, cls)

combine_dicts

Combines several AttrDicts recursively. This can be used to combine a given configuration with a default configuration.

Example

import exputils as eu

dict_a = eu.AttrDict(name='a', x_val=1)
dict_b = eu.AttrDict(name='default', x_val=0, y_val=0)

comb_dict = eu.combine_dicts(dict_a, dict_b)

print(comb_dict)
Output:
AttrDict({'name': 'a', 'x_val': 1, 'y_val': 0})

Parameters:

Name Type Description Default
*args

Dictionaries that should be combined. The order is important as a dictionary that is given first overwrites the values of properties of all following dictionaries.

()
is_recursive bool

Should the dictionaries be recursively combined?

True
copy_mode str

Defines how the properties of the dictionaries should be copied ('deepcopy', 'copy', 'none') to the combined dictionary.

'deepcopy'

Returns:

Name Type Description
comb_dict AttrDict

Combined AttrDict.

Source code in exputils/misc/attrdict.py
def combine_dicts(*args,
                  is_recursive: bool = True,
                  copy_mode: str = 'deepcopy') -> AttrDict:
    """
    Combines several AttrDicts recursively.
    This can be used to combine a given configuration with a default configuration.

    Example:
        ```python
        import exputils as eu

        dict_a = eu.AttrDict(name='a', x_val=1)
        dict_b = eu.AttrDict(name='default', x_val=0, y_val=0)

        comb_dict = eu.combine_dicts(dict_a, dict_b)

        print(comb_dict)
        ```
        Output:
        ```
        AttrDict({'name': 'a', 'x_val': 1, 'y_val': 0})
        ```

    Parameters:
        *args:
            Dictionaries that should be combined.
            The order is important as a dictionary that is given first overwrites the values of
            properties of all following dictionaries.
        is_recursive (bool):
            Should the dictionaries be recursively combined?
        copy_mode:
            Defines how the properties of the dictionaries should be copied ('deepcopy', 'copy', 'none')
            to the combined dictionary.

    Returns:
        comb_dict (AttrDict): Combined AttrDict.
    """

    args = list(args)

    # convert arguments to AttrDicts if they are not
    for idx in range(len(args)):
        if args[idx] is None:
            args[idx] = AttrDict()
        elif not isinstance(args[idx], AttrDict):
            args[idx] = AttrDict.from_dict(args[idx])

    # copy the dictionaries according to copy mode
    dicts = []
    for dict in args:
        if copy_mode.lower() == 'deepcopy':
            dicts.append(deepcopy(dict))
        elif copy_mode.lower() == 'copy':
            dicts.append(dict.copy())
        elif copy_mode is None or copy_mode.lower() == 'none':
            dicts.append(dict)
        else:
            raise ValueError('Unknown copy mode {!r}!'.format(copy_mode))

    # combine the dicts going from last to first
    for dict_idx in range(len(args)-1, 0, -1):

        for def_key, def_item in dicts[dict_idx].items():

            if not def_key in dicts[dict_idx-1]:
                # add default item if not found target
                dicts[dict_idx - 1][def_key] = def_item
            elif (is_recursive
                  and isinstance(def_item, Mapping)
                  and isinstance(dicts[dict_idx - 1][def_key], Mapping)):
                # If the value is a dictionary in the default and the target, then also set default
                # values for it.
                dicts[dict_idx - 1][def_key] = combine_dicts(dicts[dict_idx - 1][def_key],
                                                             def_item,
                                                             is_recursive=is_recursive,
                                                             copy_mode=copy_mode)

    return dicts[0]

create_object_from_config

Creates a class object that is defined as a config dictionary or AttrDict.

The configuration dictionary must contain a 'cls' property that holds the class type. All other properties are used as parameters for the constructor of the object.

Example

import exputils as eu
from collections import Counter

config = eu.AttrDict()
config.cls = Counter  # class type that should be created
config.green=2
config.blue=1

# creates the Counter object using: obj = Counter(red=3, green=2, blue=1)
obj = eu.create_object_from_config(config, red=3)

print(obj)
Output:
Counter({'red': 3, 'green': 2, 'blue': 1})

Parameters:

Name Type Description Default
config dict

Configuration dictionary with cls property that holds the class type.

required
*args

Additional arguments to pass to the constructor of the object.

()
*argv

Additional arguments to pass to the constructor of the object.

{}

Returns:

Name Type Description
obj object

The resulting object.

Source code in exputils/misc/misc.py
def create_object_from_config(config: dict, *args, **argv):
    """
    Creates a class object that is defined as a config dictionary or AttrDict.

    The configuration dictionary must contain a 'cls' property that holds the class type.
    All other properties are used as parameters for the constructor of the object.

    Example:
        ```python
        import exputils as eu
        from collections import Counter

        config = eu.AttrDict()
        config.cls = Counter  # class type that should be created
        config.green=2
        config.blue=1

        # creates the Counter object using: obj = Counter(red=3, green=2, blue=1)
        obj = eu.create_object_from_config(config, red=3)

        print(obj)
        ```
        Output:
        ```
        Counter({'red': 3, 'green': 2, 'blue': 1})
        ```

    Parameters:
        config (dict): Configuration dictionary with `cls` property that holds the class type.
        *args: Additional arguments to pass to the constructor of the object.
        *argv: Additional arguments to pass to the constructor of the object.

    Returns:
        obj (object): The resulting object.
    """
    return call_function_from_config(config, *args, func_attribute_name='cls', **argv)

call_function_from_config

Calls a function that is defined as a config dictionary or AttrDict.

The configuration dictionary must contain a property that holds the function handle. This is by default called func, but can be differently defined using the func_attribute_name parameter. All other properties of the configuration dictionary are used as parameters for the function call.

If the given config argument is a function handle, then this function is called and the given args and *argvs given as arguments. If the given config argument is not a dictionary or a function handle, then it is directly returned.

Example

import exputils as eu

def calc_area(length, width, unit='sm'):
    area = length * width
    return f"{area} {unit}"

config = eu.AttrDict()
config.func = calc_area  # function that should be called
config.unit = 'square meters'

# calls the function with: calc_area(length=3, width=4, unit='square meters')
out = eu.call_function_from_config(config, length=3, width=4)

print(out)
Output:
'12 square meters'

Parameters:

Name Type Description Default
config dict

Configuration dictionary func property that holds the function handle.

required
func_attribute_name str

Name of the func attribute.

'func'
*args

Additional arguments to pass to the function.

()
*argv

Additional arguments to pass to the function.

{}

Returns:

Name Type Description
res Any

The return value of the function.

Source code in exputils/misc/misc.py
def call_function_from_config(config, *args, func_attribute_name='func', **argv):
    """Calls a function that is defined as a config dictionary or AttrDict.

    The configuration dictionary must contain a property that holds the function handle. This is
    by default called `func`, but can be differently defined using the `func_attribute_name` parameter.
    All other properties of the configuration dictionary are used as parameters for the function call.

    If the given `config` argument is a function handle, then this function is called and the given
    *args and **argvs given as arguments.
    If the given `config` argument is not a dictionary or a function handle, then it is directly
    returned.

    Example:
        ```python
        import exputils as eu

        def calc_area(length, width, unit='sm'):
            area = length * width
            return f"{area} {unit}"

        config = eu.AttrDict()
        config.func = calc_area  # function that should be called
        config.unit = 'square meters'

        # calls the function with: calc_area(length=3, width=4, unit='square meters')
        out = eu.call_function_from_config(config, length=3, width=4)

        print(out)
        ```
        Output:
        ```
        '12 square meters'
        ```

    Parameters:
        config (dict): Configuration dictionary func property that holds the function handle.
        func_attribute_name (str): Name of the func attribute.
        *args: Additional arguments to pass to the function.
        *argv: Additional arguments to pass to the function.

    Returns:
        res (Any): The return value of the function.
    """

    if isinstance(config, dict) and func_attribute_name in config:

        func_handle = config[func_attribute_name]

        function_arguments = copy.deepcopy(config)
        del function_arguments[func_attribute_name]
        function_arguments = combine_dicts(argv, function_arguments)

        return func_handle(*args, **function_arguments)

    elif callable(config):
        return config(*args, **argv)

    else:
        return config

seed

Sets the random seed for random, numpy and pytorch (if it is installed).

Parameters:

Name Type Description Default
seed (int, dict)

Seed (integer) or a configuration dictionary which contains a 'seed' property. If None is given, a random seed is chosen.

None
is_set_random bool

Should the random seed of the python random package be set.

True
is_set_numpy bool

Should random seed of numpy.random be set.

True
is_set_torch bool

Should random seed of torch be set.

True

Returns:

Name Type Description
seed int

Integer that was used as seed.

Source code in exputils/misc/misc.py
def seed(seed: Optional[Union[int, dict]] = None,
         is_set_random: bool = True,
         is_set_numpy: bool = True,
         is_set_torch: bool = True) -> int:
    """
    Sets the random seed for random, numpy and pytorch (if it is installed).

    Parameters:
        seed (int, dict):
            Seed (integer) or a configuration dictionary which contains a 'seed' property.
            If `None` is given, a random seed is chosen.
        is_set_random (bool):
            Should the random seed of the python `random` package be set.
        is_set_numpy (bool):
            Should random seed of `numpy.random` be set.
        is_set_torch:
            Should random seed of torch be set.

    Returns:
        seed (int): Integer that was used as seed.
    """

    if seed is None:
        if torch:
            seed = torch.seed()
        else:
            seed = np.randint(2**32)

    elif isinstance(seed, dict):
        seed = seed.get('seed', None)

    if is_set_numpy:
        np.random.seed(seed)

    if is_set_random:
        random.seed(seed)

    if torch:
        if is_set_torch:
            torch.manual_seed(seed)

    return seed

update_status

Updates the status of the running experiment/repetition in its status file.

Parameters:

Name Type Description Default
status str

Status in form of a string.

required
status_file str

Optional path to the status file. By default, it is the status file of the running process.

None
Source code in exputils/misc/misc.py
def update_status(status: str,
                  status_file: Optional[str] = None):
    """
    Updates the status of the running experiment/repetition in its status file.

    Parameters:
        status (str): Status in form of a string.
        status_file (str):
            Optional path to the status file.
            By default, it is the status file of the running process.
    """

    if status_file is None:
        status_file = os.environ.get('EU_STATUS_FILE', default=None)

    if status_file is None or status_file == '':
        warnings.warn('Can not find status file location to update its status.')
    else:
        time_str = datetime.now().strftime("%Y/%m/%d %H:%M:%S")
        with open(status_file, 'a+') as file:
            file.write(time_str + "\n" + "running " + status + "\n")

Default Variables

The package has a list of default variables located on the module level that mainly control the names of the generated directories. They can be adjusted if needed.

Name Type Description Default
DEFAULT_ODS_CONFIGURATION_FILE str Filename of the ODS configuration file for campaigns. 'experiment_configurations.ods'
DEFAULT_EXPERIMENTS_DIRECTORY str Name of the directory in the campaign directory under which experiment directories are created. 'experiments'
EXPERIMENT_DIRECTORY_TEMPLATE str Name template of experiment directories. Has to contain a placeholder for the ID. 'experiment_{:06d}'
REPETITION_DIRECTORY_TEMPLATE str Name template of repetition directories. Has to contain a placeholder for the ID. 'repetition_{:06d}'
DEFAULT_DATA_DIRECTORY str Name of the directory that is used to store the logs under each repetition. 'data'
REPETITION_DATA_KEY str Keyname of the element in the AttrDict returned by the load_experiment_data function that holds all the repetition data. 'repetition_data'

To customize them they can be changed after the exputils package as been imported:

import exputils as eu
# use a shorter form for experiment and repetition directories
eu.EXPERIMENT_DIRECTORY_TEMPLATE = 'exp_{:06d}' 
eu.REPETITION_DIRECTORY_TEMPLATE = 'rep_{:06d}'