Cartesian product akin to :func:`itertools.product`, but accepting
    generator functions

    Unlike :func:`itertools.product` this function does not convert the input
    iterables into tuples. Thus, it can handle large or infinite inputs. As a
    drawback, however, it only works with "restartable" iterables (something
    that :func:`iter` can repeatably turn into an iterator, or a generator
    function (but not the generator iterator that is returned by that
    generator function)

    Args:
        generators: list of restartable iterators or generator functions
        repeat: number of times `generators` should be repeated

    Adapted from https://stackoverflow.com/q/12093364/

def product(*generators, repeat=1):
    """Cartesian product akin to :func:`itertools.product`, but accepting
    generator functions

    Unlike :func:`itertools.product` this function does not convert the input
    iterables into tuples. Thus, it can handle large or infinite inputs. As a
    drawback, however, it only works with "restartable" iterables (something
    that :func:`iter` can repeatably turn into an iterator, or a generator
    function (but not the generator iterator that is returned by that
    generator function)

    Args:
        generators: list of restartable iterators or generator functions
        repeat: number of times `generators` should be repeated

    Adapted from https://stackoverflow.com/q/12093364/
    """
    if len(generators) == 0:
        yield ()
    else:
        generators = generators * repeat
        it = generators[0]
        for item in it() if callable(it) else iter(it):
            for items in product(*generators[1:]):
                yield (item, ) + items