docs: def iter_rate(self):
          """
          pun not intended
  
          # TODO: record iteration times for analysis
          # TODO Incorporate this better
          # FIXME; pad_stdout into subfunctions
  
          import dis
          dis.dis(ut.ProgressIter.iter_rate)
          """
          #class IterState(object):
          #    def __init__(state):
          #        state.freq = 1
          #        state.freq = 1
          #        pass
          adjust = self.autoadjust
          self._cursor_at_newline = not self.backspace
          # SETUP VARIABLES
          # HACK: reaquire logging print funcs in case they have changed
          if self.stream is None:
              self.write = util_logging._utool_write()
              self.flush = util_logging._utool_flush()
          else:
              self.write = lambda msg: self.stream.write(msg)  # NOQA
              self.flush = lambda: self.stream.flush()  # NOQA
  
          length        = self.length * self.parent_length  # hack
          freq          = self.freq
          self.count    = 0
          between_count = 0
          last_count    = 0
  
          # how long iterations should be before a flush
          # (used for freq adjustment)
          time_thresh = (self._get_timethresh_heuristics()
                         if self.time_thresh is None else
                         self.time_thresh)
          time_thresh_growth = self.time_thresh_growth
          if time_thresh_growth > 1:
              # time_thresh_growth is specified for very long processes
              # print out the starting timestamp in that case
              timestamp = time.strftime('%Y-%m-%d %H:%M:%S') + ' ' + time.tzname[0]
              print('Start progress lbl= %s at %s' % (self.lbl, timestamp,))
          #time_thresh = 0.5
          max_between_time = -1.0
          max_between_count = -1.0  # why is this different? # because frequency varies
  
          # TODO: should be kept as a statistic that uses the max time from a
          # list of iterations divided by the size of that list that will account
          # for buffering issues
          iters_per_second = 0
          self.iters_per_second = float('nan')
          self.est_seconds_left = 0
          self.total_seconds = 0
  
          # Write initial message
          #force_newlines = not self.backspace
          start_msg_fmt = ''.join(self.build_msg_fmtstr_head_cols(length, self.lbl))
          self.msg_fmtstr = self.build_msg_fmtstr2(self.lbl, length,
                                                   self.invert_rate,
                                                   self.backspace)
  
          try:
              util_logging._utool_flush()()
          except IOError as ex:
              # There is some weird error when doing progress in IPython notebook
              if util_arg.VERBOSE:
                  print('IOError flushing %s' % (ex,))
          if not self.prehack:
              if self.backspace:
                  self.display_message()
              elif self.verbose:
                  start_msg = start_msg_fmt.format(count=self.parent_offset)
                  util_logging._utool_write()(start_msg + '\n')
  
              self._cursor_at_newline = not self.backspace
  
              try:
                  util_logging._utool_flush()()
              except IOError as ex:
                  # There is some weird error when doing progress in IPython notebook
                  if util_arg.VERBOSE:
                      print('IOError flushing %s' % (ex,))
          else:
              self._cursor_at_newline = True
  
          if self.prog_hook is not None:
              self.prog_hook(self.count, length)
  
          # TODO: on windows is time.clock better?
          # http://exnumerus.blogspot.com/2011/02/how-to-quickly-plot-multiple-line.html
          start_time    = default_timer()
          last_time     = start_time
  
          start = 1 + self.parent_offset
  
          if self.freq_est_strat == 'between':
              FREQ_EST = 0
          elif self.freq_est_strat == 'absolute':
              FREQ_EST = 1
          else:
              FREQ_EST = 1
  
          USE_RECORD = True
  
          # use last 64 times to compute a more stable average rate
          measure_between_time = collections.deque([], maxlen=self.est_window)
  
          # Wrap the for loop with a generator
          for self.count, item in enumerate(self.iterable, start=start):
              if self.prehack:
                  # hack to print before yeilding
                  # so much for efficiency
                  self.set_extra((self.lbl + '=' + self.prehack) % item)
                  self.display_message()
                  self.ensure_newline()
  
              # GENERATE
              yield item
  
              if self.prehack or (self.count) % freq == 0:
                  now_time          = default_timer()
                  between_time      = (now_time - last_time)
                  between_count     = self.count - last_count
                  total_seconds     = (now_time - start_time)
                  self.total_seconds = total_seconds
                  if FREQ_EST == 0:
                      if USE_RECORD:
                          measure_between_time.append(between_count / (float(between_time) + 1E-9))
                          iters_per_second = sum(measure_between_time) / len(measure_between_time)
                      else:
                          iters_per_second = between_count / (float(between_time) + 1E-9)
                  elif FREQ_EST == 1:
                      iters_per_second = (now_time - start_time) / self.count
  
                  self.iters_per_second = iters_per_second
                  # If the future is known
                  if length is None:
                      est_seconds_left = -1
                  else:
                      iters_left = length - self.count
                      est_seconds_left = iters_left / (iters_per_second + 1E-9)
                  self.est_seconds_left = est_seconds_left
  
                  # /future
                  last_count        = self.count
                  last_time         = now_time
                  # ADJUST FREQ IF NEEDED
                  # Adjust frequency if printing too quickly
                  # so progress doesnt slow down actual function
                  # TODO: better adjust algorithm
                  time_thresh *= time_thresh_growth
                  if adjust and (between_time < time_thresh or between_time > time_thresh * 2.0):
                      max_between_time = max(max(max_between_time, between_time),
                                             1E-9)
                      max_between_count = max(max_between_count, between_count)
                      # If progress was uniform and all time estimates were
                      # perfect this would be the new freq to achieve time_thresh
                      new_freq = max(int(time_thresh * max_between_count /
                                         max_between_time), 1)
                      if DEBUG_FREQ_ADJUST:
                          print('\n+---')
                          print('[prog] between_count = %r' % between_count)
                          print('[prog] between_time = %.8r' % between_time)
                          print('[prog] time_thresh = %r' % time_thresh)
                          print('[prog] max_between_count = %r' % max_between_count)
                          print('[prog] max_between_time = %.8r' % max_between_time)
                          print('[prog] Adusting frequency from: %r' % freq)
                          print('[prog] Adusting frequency to: %r' % new_freq)
                          print('L___')
                      # But things are not perfect. So, don't make drastic changes
                      max_freq_change_up = max(256, freq * 2)
                      max_freq_change_down = freq // 2
                      if (new_freq - freq) > max_freq_change_up:
                          freq += max_freq_change_up
                      elif (freq - new_freq) > max_freq_change_down:
                          freq -= max_freq_change_down
                      else:
                          freq = new_freq
  
                  if not self.prehack:
                      self.display_message()
  
                  # DO PROGRESS INFO
                  if self.prog_hook is not None:
                      # From the point of view of the progress iter, we are about
                      # to enter the body of a for loop. (But we may have
                      # executed the body implicitly in the yeild....  so it is
                      # ambiguous. In the second case 0 will be executed twice.
                      self.prog_hook(self.count, length)
  
          if self.prehack:
              self.set_extra('')
  
          # --- end of main loop
          # cleanup
          if (self.count) % freq != 0:
              # If the final line of progress was not written in the loop, write
              # it here
              self.est_seconds_left = 0
              self.total_seconds = (default_timer() - start_time)
              self.display_message()
              if self.prog_hook is not None:
                  # From the point of view of the progress iter, we are about to
                  # enter the body of a for loop. (But we may have executed the
                  # body implicitly in the yeild....  so it is ambiguous. In the
                  # second case 0 will be executed twice.
                  self.prog_hook(self.count, length)
  
          self.ensure_newline()