pytb.itertools module

Flexibly test a number possible configurations of a function

Assume you have a function that takes a number of parameters:

>>> def my_func(a, b, c=2, **kwargs):
...    print(' '.join((a, b, c)), kwargs)

And you want to call it with multiple parameter combinations

>>> my_params = {
...     'a': 'a1',
...     'b': ('b1','b2'),
...     'c': ('c1', 'c2'),
...     'additional_arg': 'val'
... }

You can use the named_tuple() function of this module to create any possible combination of the provided parameters

>>> for params in named_product(my_params):
...     my_func(**params)
a1 b1 c1 {'additional_arg': 'val'}
a1 b1 c2 {'additional_arg': 'val'}
a1 b2 c1 {'additional_arg': 'val'}
a1 b2 c2 {'additional_arg': 'val'}

Excluding some combinations

If some parameter combinations are not allowed, you can use the functions ability to work with nested dicts to overwrite values defined in an outer dict

>>> my_params = {
...     'a': 'a1',
...     'b': ('b1','b2'),
...     'c': {
...         'c1': {'b': 'b1'},
...         'c2': {},
...         'c3': {
...             'additional_arg': 'other val',
...             'another arg': 'yet another val'}
...     },
...     'additional_arg': 'val'
... }
>>> for params in named_product(my_params):
...     my_func(**params)
a1 b1 c1 {'additional_arg': 'val'}
a1 b1 c2 {'additional_arg': 'val'}
a1 b2 c2 {'additional_arg': 'val'}
a1 b1 c3 {'additional_arg': 'other val', 'another arg': 'yet another val'}
a1 b2 c3 {'additional_arg': 'other val', 'another arg': 'yet another val'}

Note that for c='c1' only b='b1' was used. You can also define new variables inside each dict that only get used for combinations in this branch.

safe_copy of values

By default, all values are (deep) copied before they are yielded from the generator. This is really useful, as otherwise any change you make to an object in any combination would change the object for all other combination.

If you have large objects in your combinations however, copying may be expensive. In this case you can use the safe_copy parameter to control if and which objects should be copied before yielding.

API Documentation

Methods to work with iterables conveniently. (methods that could be in the python stdlib itertools package)

pytb.itertools.named_product(values: Optional[Mapping[Any, Any]] = None, repeat: int = 1, safe_copy: Union[Sequence[str], bool] = True, **kwargs) → Generator[Any, None, None][source]

Return each possible combination of the input parameters (cartesian product), thus this provides the same basic functionality of :meth:itertools.product. However this method provides more flexibility as it:

  1. returns dicts instead of tuples
>>> list(named_product(a=('X', 'Y'), b=(1, 2)))
[{'a': 'X', 'b': 1}, {'a': 'X', 'b': 2}, {'a': 'Y', 'b': 1}, {'a': 'Y', 'b': 2}]
  1. accepts either a dict or kwargs
>>> list(named_product({ 'a':('X', 'Y') }, b=(1, 2)))
[{'a': 'X', 'b': 1}, {'a': 'X', 'b': 2}, {'a': 'Y', 'b': 1}, {'a': 'Y', 'b': 2}]
  1. accepts nested dicts
>>> list(named_product(
...     a=(
...             {'X': {'b':(1,2)}},
...             {'Y': {
...                     'b': (3, 4),
...                     'c': (5, )
...                   }
...             }
...     )
... ))
[{'a': {'X': {'b': (1, 2)}}}, {'a': {'Y': {'b': (3, 4), 'c': (5,)}}}]
  1. accepts scalar values
>>> list(named_product(b='Xy', c=('a', 'b')))
[{'b': 'Xy', 'c': 'a'}, {'b': 'Xy', 'c': 'b'}]
  • values – a dict of iterables used to create the cartesian product
  • repeat – repeat iteration of the product N-times
  • safe_copy – copy all values before yielding any combination. If passed True all values are copied. If passed False no values are copied. If passed an iterable of strings, only the values whose key is in the iterable are copied.
  • **kwargs – optional keyword arguments. The dict of keyword arguments is merged with the values dict, with kwargs overwriting values in values