docs: 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 iterable is not a function, load the whole thing
      # into a list so it can be ran over multiple times
      if not callable_base:
          iterable = tuple(iterable)
      # these store timestamps for time calculations
      durations = []
      results = []
      for i,_ in enumerate(steps):
          current_tasks = steps[:i+1]
          #print('testing',current_tasks)
          duration = 0.0
          # run this test x number of times
          for t in range(100000):
              # build the generator
              test_generator = iter(iterable()) if callable_base else iter(iterable)
              # time its execution
              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])
              #print(durations[0],durations[0])
          else:
              results.append(durations[-1]-durations[-2])
              #print(durations[-1]-durations[-2],durations[-1])
      #print(results)
      #print(durations)
      assert sum(results) > 0
      resultsum = sum(results)
      ratios = [i/resultsum for i in results]
      #print(ratios)
      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))