32

I have a list of dicts with the fields classid, dept, coursenum, area, and title from a sql query. I would like to output the values in a human readable format. I was thinking a Column header at the top of each and then in each column the approrpiate output ie:

CLASSID     DEPT     COURSE NUMBER        AREA     TITLE
foo         bar      foo                  bar      foo
yoo         hat      yoo                  bar      hat

(obviously with standard alignment/spacing)

How would I accomplish this in python?

pradyunsg
  • 18,287
  • 11
  • 43
  • 96
themaestro
  • 13,750
  • 20
  • 56
  • 75
  • If you need to print an aligned table *without a header*, see here: [How to print a list of dicts as an aligned table?](https://stackoverflow.com/questions/53005400/how-to-print-a-list-of-dicts-as-an-aligned-table) – Georgy Feb 02 '19 at 13:30
  • You can try the [Padnums module](http://ginstrom.com/scribbles/2007/09/04/pretty-printing-a-table-in-python/). The example output seems to match what you are requesting. Or this [table indentation method](http://code.activestate.com/recipes/267662-table-indentation/). I haven't used either, but they were in the first few result of googling ["python pretty print table"](http://www.google.com/search?q=python+pretty+print+table) – unholysampler Feb 22 '11 at 22:33

5 Answers5

68

Standard Python string formatting may suffice.

# assume that your data rows are tuples
template = "{0:8}|{1:10}|{2:15}|{3:7}|{4:10}" # column widths: 8, 10, 15, 7, 10
print template.format("CLASSID", "DEPT", "COURSE NUMBER", "AREA", "TITLE") # header
for rec in your_data_source: 
  print template.format(*rec)

Or

# assume that your data rows are dicts
template = "{CLASSID:8}|{DEPT:10}|{C_NUM:15}|{AREA:7}|{TITLE:10}" # same, but named
print template.format( # header
  CLASSID="CLASSID", DEPT="DEPT", C_NUM="COURSE NUMBER", 
  AREA="AREA", TITLE="TITLE"
) 
for rec in your_data_source: 
  print template.format(**rec)

Play with alignment, padding, and exact format specifiers to get best results.

9000
  • 39,899
  • 9
  • 66
  • 104
11
class TablePrinter(object):
    "Print a list of dicts as a table"
    def __init__(self, fmt, sep=' ', ul=None):
        """        
        @param fmt: list of tuple(heading, key, width)
                        heading: str, column label
                        key: dictionary key to value to print
                        width: int, column width in chars
        @param sep: string, separation between columns
        @param ul: string, character to underline column label, or None for no underlining
        """
        super(TablePrinter,self).__init__()
        self.fmt   = str(sep).join('{lb}{0}:{1}{rb}'.format(key, width, lb='{', rb='}') for heading,key,width in fmt)
        self.head  = {key:heading for heading,key,width in fmt}
        self.ul    = {key:str(ul)*width for heading,key,width in fmt} if ul else None
        self.width = {key:width for heading,key,width in fmt}

    def row(self, data):
        return self.fmt.format(**{ k:str(data.get(k,''))[:w] for k,w in self.width.iteritems() })

    def __call__(self, dataList):
        _r = self.row
        res = [_r(data) for data in dataList]
        res.insert(0, _r(self.head))
        if self.ul:
            res.insert(1, _r(self.ul))
        return '\n'.join(res)

and in use:

data = [
    {'classid':'foo', 'dept':'bar', 'coursenum':'foo', 'area':'bar', 'title':'foo'},
    {'classid':'yoo', 'dept':'hat', 'coursenum':'yoo', 'area':'bar', 'title':'hat'},
    {'classid':'yoo'*9, 'dept':'hat'*9, 'coursenum':'yoo'*9, 'area':'bar'*9, 'title':'hathat'*9}
]

fmt = [
    ('ClassID',       'classid',   11),
    ('Dept',          'dept',       8),
    ('Course Number', 'coursenum', 20),
    ('Area',          'area',       8),
    ('Title',         'title',     30)
]

print( TablePrinter(fmt, ul='=')(data) )

produces

ClassID     Dept     Course Number        Area     Title                         
=========== ======== ==================== ======== ==============================
foo         bar      foo                  bar      foo                           
yoo         hat      yoo                  bar      hat                           
yooyooyooyo hathatha yooyooyooyooyooyooyo barbarba hathathathathathathathathathat
Hugh Bothwell
  • 55,315
  • 8
  • 84
  • 99
9

You can simply left justify the string to a certain number of characters if you want to keep it simple:

print string1.ljust(20) + string2.ljust(20)
Evan Jensen
  • 153
  • 2
  • 5
4

This function takes list comprehension to a bit of an extreme, but it accomplishes what you're looking for with optimal performance:

algorithm:

  1. find longest field in each column; i.e., 'max(map(len, column_vector))'
  2. for each field (left to right, top to bottom), call str.ljust to align it to the left boundary of the column it belongs to.
  3. join fields with desired amount of separating whitespace (creating a row).
  4. join collection of rows with a newline.

row_collection: list of iterables (dicts/sets/lists), each containing data for one row.

key_list: list that specifies what keys/indices to read from each row to form columns.

def getPrintTable(row_collection, key_list, field_sep=' '*4):
  return '\n'.join([field_sep.join([str(row[col]).ljust(width)
    for (col, width) in zip(key_list, [max(map(len, column_vector))
      for column_vector in [ [v[k]
        for v in row_collection if k in v]
          for k in key_list ]])])
            for row in row_collection])
1

For me the simplest way is to convert to array of array:

datas = [
  [ 'ClassID', 'Dept', 'Course Number', 'Area', 'Title' ],
  [ 'foo', 'bar', 'foo', 'bar', 'foo' ],
  [ 'yoo', 'hat', 'yoo', 'bar', 'hat' ],
  [ 'line', 'last', 'fun', 'Lisa', 'Simpson' ]
]

for j, data in enumerate(datas):
  if j == 0:
    # print(list(data))
    max = 0
    for i in datas:
      max_len =  len(''.join(i))
      if max_len > max:
        max = max_len
        max = max + 4 * len(datas[0])
    max = 79
    print(f"max is {max}")
    print('+' + '-' * max + '+')
    v1, v2, v3, v4, v5 = datas[0]
    print(f"|{v1:^15s}|{v2:^15s}|{v3:^15s}|{v4:^15s}|{v5:^15s}|")
    print('+' +  '-' * max + '+')
    continue
  else:
    # print( '+' + '-' * max + '+')
    v1, v2, v3, v4, v5 = data
    print(f"|{v1:^15s}|{v2:^15s}|{v3:^15s}|{v4:^15s}|{v5:^15s}|")
    print('+' +  '-' * max + '+')
    # print(type(data))

You get this :

+-------------------------------------------------------------------------------+
|    ClassID    |     Dept      | Course Number |     Area      |     Title     |
+-------------------------------------------------------------------------------+
|      foo      |      bar      |      foo      |      bar      |      foo      |
+-------------------------------------------------------------------------------+
|      yoo      |      hat      |      yoo      |      bar      |      hat      |
+-------------------------------------------------------------------------------+
|     line      |     last      |      fun      |     Lisa      |    Simpson    |
+-------------------------------------------------------------------------------+
dmx
  • 1,862
  • 3
  • 26
  • 48