def time_pipeline(iterable, *steps):
    '''
This times the steps in a pipeline. Give it an iterable to test against
followed by the steps of the pipeline seperated in individual functions.
Example Usage:
```
    from random import choice, randint
    l = [randint(0,50) for i in range(100)]
    step1 = lambda iterable:(i for i in iterable if i%5==0)
    step2 = lambda iterable:(i for i in iterable if i%8==3)
    step3 = lambda iterable:sorted((1.0*i)/50 for i in iterable)
    step4 = lambda iterable:(float(float(float(float(i*3)))) for i in iterable)
    print('filter first')
    time_pipeline(l, step1, step2, step3, step4)
    print('process first')
    time_pipeline(l, step3, step4, step1, step2)
    print('filter, process, filter, process')
    time_pipeline(l, step1, step3, step2, step4)
``` 
Outputs:
    filter first
    step 1 | 2.0427s | step1 = lambda iterable:(i for i in iterable if i%5==0)
    step 2 | 2.0510s | step2 = lambda iterable:(i for i in iterable if i%8==3)
    step 3 | 2.4839s | step3 = lambda iterable:sorted((1.0*i)/50 for i in iterable)
    step 4 | 2.8446s | step4 = lambda iterable:(float(float(float(float(i*3)))) for i in iterable)
    process first
    step 1 | 7.5291s | step3 = lambda iterable:sorted((1.0*i)/50 for i in iterable)
    step 2 | 20.6732s | step4 = lambda iterable:(float(float(float(float(i*3)))) for i in iterable)
    step 3 | 16.8470s | step1 = lambda iterable:(i for i in iterable if i%5==0)
    step 4 | 16.8269s | step2 = lambda iterable:(i for i in iterable if i%8==3)
    filter, process, filter, process
    step 1 | 2.0528s | step1 = lambda iterable:(i for i in iterable if i%5==0)
    step 2 | 3.3039s | step3 = lambda iterable:sorted((1.0*i)/50 for i in iterable)
    step 3 | 3.1385s | step2 = lambda iterable:(i for i in iterable if i%8==3)
    step 4 | 3.1489s | step4 = lambda iterable:(float(float(float(float(i*3)))) for i in iterable)
'''
    if callable(iterable):
        try:
            iter(iterable())
            callable_base = True
        except:
            raise TypeError('time_pipeline needs the first argument to be an iterable or a function that produces an iterable.')
    else:
        try:
            iter(iterable)
            callable_base = False
        except:
            raise TypeError('time_pipeline needs the first argument to be an iterable or a function that produces an iterable.')
    if not callable_base:
        iterable = tuple(iterable)
    durations = []
    results = []
    for i,_ in enumerate(steps):
        current_tasks = steps[:i+1]
        duration = 0.0
        for t in range(100000):
            test_generator = iter(iterable()) if callable_base else iter(iterable)
            start = ts()
            for task in current_tasks:
                test_generator = task(test_generator)
            for i in current_tasks[-1](test_generator):
                pass
            duration += ts() - start
        durations.append(duration)
        if len(durations) == 1:
            results.append(durations[0])
        else:
            results.append(durations[-1]-durations[-2])
    assert sum(results) > 0
    resultsum = sum(results)
    ratios = [i/resultsum for i in results]
    for i in range(len(ratios)):
        try:
            s = getsource(steps[i]).splitlines()[0].strip()
        except:
            s = repr(steps[i]).strip()
        print('step {} | {:2.4f}s | {}'.format(i+1, durations[i], s))