docs: def tabulate(data,  # type: List[List[Any]]
               header=None,  # type: Optional[List[Any]]
               col_align=None,  # type: Union[str, List[str]]
               ):
      # type: (...) -> List[str]
      """ Format data as a table without any fancy features.
      col_align: l/r/c or a list/string of l/r/c. l = left, r = right, c = center
      Return a list of strings (lines of the table).
      """
      if not data and not header:
          return []
      if data:
          n_cols = len(data[0])
      else:
          assert header is not None
          n_cols = len(header)
      if not all(len(row) == n_cols for row in data):
          raise ValueError('data is not rectangular')
  
      if col_align is None:
          col_align = ['l'] * n_cols
      elif isinstance(col_align, six.string_types) and len(col_align) == 1:
          col_align = [col_align] * n_cols
      else:
          col_align = list(col_align)
          if len(col_align) != n_cols:
              raise ValueError('col_align length does not match number of columns')
  
      if header and len(header) != n_cols:
          raise ValueError('header length does not match number of columns')
  
      if header:
          data = [header] + data
      data = [[six.text_type(x) for x in row] for row in data]
      col_width = [max(len(row[col_i]) for row in data) for col_i in range(n_cols)]
      if header:
          data.insert(1, ['-' * width for width in col_width])
  
      line_tpl = u'  '.join(
          u'{:%s%s}' % ({'l': '', 'r': '>', 'c': '^'}[align], width)
          for align, width in zip(col_align, col_width))
      return [line_tpl.format(*row) for row in data]