Welcome, guest | Sign In | My Account | Store | Cart

Sometime pprint module is not enough for formatting data for console or log file output. This module provide function which fill the gap.

Sample function call:

nums = [ '1', '2', '3', '4' ]
speeds = [ '100', '10000', '1500', '12' ]
desc = [ '', 'label 1', 'none', 'very long description' ]
lines = format_table( [(nums, ALIGN_RIGHT|PADDING_ALL, 'NUM'), 
                       (speeds, ALIGN_RIGHT|PADDING_ALL, 'SPEED'), 
                       (desc, ALIGN_LEFT|PADDING_ALL, 'DESC')] )

Output:

=======================================
| NUM | SPEED | DESC                  |
=======================================
|   1 |   100 |                       |
|   2 | 10000 | label 1               |
|   3 |  1500 | none                  |
|   4 |    12 | very long description |
=======================================
Python, 118 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# -*- coding: Windows-1251 -*-
'''
texttable -- render text tables for console, log files etc
'''
__author__ = 'Denis Barmenkov <denis.barmenkov@gmail.com>'
__source__ = 'http://code.activestate.com/recipes/577202-render-tables-for-text-interface/'

import types

# Align center is not supported because of the terrible form :)
ALIGN_RIGHT   = 0x0001
ALIGN_LEFT    = 0x0002
PADDING_LEFT  = 0x0010
PADDING_RIGHT = 0x0020
PADDING_ALL   = PADDING_LEFT | PADDING_RIGHT

def _rpad_to_maxlen(slist, padchar=' '):
    maxlen = max(map(len, slist))
    res = map(lambda s: s + padchar * (maxlen - len(s)), slist)
    return res

def _lpad_to_maxlen(slist, padchar=' '):
    maxlen = max(map(len, slist))
    res = map(lambda s: padchar * (maxlen - len(s)) + s, slist)
    return res

def test_mask(mask, flag):
    '''
    tests mask for all bits of flag set
    '''
    return mask & flag == flag

def _align_para(para, mode):
    if test_mask(mode, ALIGN_RIGHT):
        para = _lpad_to_maxlen(para) # little confusing: right align == left padding
    elif test_mask(mode, ALIGN_LEFT):
        para = _rpad_to_maxlen(para)
    else:
        raise ValueError, "NO ALIGN SPECIFIED!"

    if test_mask(mode, PADDING_LEFT):
        para = map(lambda x: ' ' + x, para)
    if test_mask(mode, PADDING_RIGHT):
        para = map(lambda x: x + ' ', para)

    return para

def _glue_cols(col1, col2):
    return map(lambda x: '%s|%s' % (x[0], x[1]), zip(col1, col2))

def _has_column_headers(cols):
    fail_count = 0
    for c in cols:
        try:
            a = c[2]
        except IndexError:
            fail_count += 1
    return fail_count == 0

def _join_to_list(*arguments):
    '''
    join all parameters to one list, 
    expanding lists and tuples
    '''
    res = list()
    for arg in arguments:
        if isinstance(arg, types.ListType):
            res.extend(arg)
        elif isinstance(arg, types.TupleType):
            res.extend(list(arg))
        else:
            res.append(arg)
    return res

def format_table(cols):
    # 1. aligning data
    if len(cols) == 0:
        return list()

    header_present = _has_column_headers(cols)
    if header_present:
        # prefix all data with headers // TODO: alignment set to center 
        cols = map(lambda x: [_join_to_list(x[2], x[0]), x[1] ], cols)

    cols2 = map(lambda x: _align_para(x[0], x[1]), cols)
    if len(cols2) == 0:
        return list()

    empty_vert_border = [ '' ] * len(cols2[0])

    cols2.append(empty_vert_border)
    cols2.insert(0, empty_vert_border)

    glued_cols = reduce(_glue_cols, cols2)
    if len(glued_cols) == 0:
        return list()

    border = '=' * len(glued_cols[0])
    glued_cols.insert(0, border)
    glued_cols.append(border)
    if header_present:
        glued_cols.insert(2, border)  # insert delimiter berween header and values        

    return glued_cols

def demo():
    nums = [ '1', '2', '3', '4' ]
    speeds = [ '100', '10000', '1500', '12' ]
    desc = [ '', 'label 1', 'none', 'very long description' ]
    lines = format_table( [(nums, ALIGN_RIGHT|PADDING_ALL, 'NUM'), 
                           (speeds, ALIGN_RIGHT|PADDING_ALL, 'SPEED'), 
                           (desc, ALIGN_LEFT|PADDING_ALL, 'DESC')] )

    print '\n'.join(lines)


if __name__ == '__main__':
    demo()

6 comments

Metal 11 years, 7 months ago  # | flag

How about this one?

class ALIGN:
    LEFT, RIGHT = '-', ''

class Column(list):
    def __init__(self, name, data, align=ALIGN.RIGHT):
        list.__init__(self, data)
        self.name = name
        width = max(len(x) for x in data + [name])
        self.format = ' %%%s%ds ' % (align, width)

class Table:
    def __init__(self, *columns):
        self.columns = columns
        self.length = max(len(x) for x in columns)
    def get_row(self, i=None):
        for x in self.columns:
            if i is None:
                yield x.format % x.name
            else:
                yield x.format % x[i]
    def get_rows(self):
        yield '|'.join(self.get_row(None))
        for i in range(0, self.length):
            yield '|'.join(self.get_row(i))

    def __str__(self):
        return '\n'.join(self.get_rows())   

def test():
    nums = [ '1', '2', '3', '4' ]
    speeds = [ '100', '10000', '1500', '12' ]
    desc = [ '', 'label 1', 'none', 'very long description' ]
    print
    print Table(
        Column('NUM', nums),
        Column('SPEED', speeds),
        Column('DESC', desc, align=ALIGN.LEFT))
test()
Denis Barmenkov (author) 11 years, 7 months ago  # | flag

Cheaper and much more clearer version, thank you!

But: padding functionality throw out yield statements are too complex (or unavailable) for old Python versions :)

Carlos del Ojo 11 years, 6 months ago  # | flag

and this one?

class Table:
    def __init__(self,title,headers,rows):
        self.title=title
        self.headers=headers
        self.rows=rows
        self.nrows=len(self.rows)
        self.fieldlen=[]

        ncols=len(headers)

        for i in range(ncols):
            max=0
            for j in rows:
                if len(str(j[i]))>max: max=len(str(j[i]))
            self.fieldlen.append(max)

        for i in range(len(headers)):
            if len(str(headers[i]))>self.fieldlen[i]: self.fieldlen[i]=len(str(headers[i]))


        self.width=sum(self.fieldlen)+(ncols-1)*3+4

    def __str__(self):
        bar="-"*self.width
        title="| "+self.title+" "*(self.width-3-(len(self.title)))+"|"
        out=[bar,title,bar]
        header=""
        for i in range(len(self.headers)):
            header+="| %s" %(str(self.headers[i])) +" "*(self.fieldlen[i]-len(str(self.headers[i])))+" "
        header+="|"
        out.append(header)
        out.append(bar)
        for i in self.rows:
            line=""
            for j in range(len(i)):
                line+="| %s" %(str(i[j])) +" "*(self.fieldlen[j]-len(str(i[j])))+" "
            out.append(line+"|")

        out.append(bar)
        return "\r\n".join(out)

print Table("This is the title",["Header1","Header2","H3"],[[1,2,3],["Hi","everybody","How are you??"],[None,True,[1,2]]])


---------------------------------------
| This is the title                   |
---------------------------------------
| Header1 | Header2   | H3            |
---------------------------------------
| 1       | 2         | 3             |
| Hi      | everybody | How are you?? |
| None    | True      | [1, 2]        |
---------------------------------------
Vasilij Pupkin 9 years, 9 months ago  # | flag

Improved version of Metal's code:

class ALIGN:
    LEFT, RIGHT = '-', ''

class Column(list):
    def __init__(self, name, data, align=ALIGN.RIGHT):
        list.__init__(self, data)
        self.name = name
        self.width = max(len(x) for x in data + [name])
        self.format = ' %%%s%ds ' % (align, self.width)

class Table:
    def __init__(self, *columns):
        self.columns = columns
        self.length = max(len(x) for x in columns)
    def get_row(self, i=None):
        for x in self.columns:
            if i is None:
                yield x.format % x.name
            else:
                yield x.format % x[i]
    def get_line(self):
        for x in self.columns:
            yield '-' * (x.width + 2)
    def join_n_wrap(self, char, elements):
        return ' ' + char + char.join(elements) + char
    def get_rows(self):
        yield self.join_n_wrap('+', self.get_line())
        yield self.join_n_wrap('|', self.get_row(None))
        yield self.join_n_wrap('+', self.get_line())
        for i in range(0, self.length):
            yield self.join_n_wrap('|', self.get_row(i))
        yield self.join_n_wrap('+', self.get_line())

    def __str__(self):
        return '\n'.join(self.get_rows())
  1. Lines added to separate header
  2. One space padding added on left of the table

Result example:

 +---------------+----------+------------+
 |          Name |     Type |     Status |
 +---------------+----------+------------+
 |      Monitor2 | t1.micro |    running |
 | Maksa reserv1 | m1.large |      sleep |
 +---------------+----------+------------+
Peter Rubenstein 9 years, 1 month ago  # | flag

Further refined for readability, consolidate to one class, and to avoid subclassing list

class Table:
    def __init__(self, *columns):
        self.columns = columns
        self.length = max(len(col.data) for col in columns)
    def get_row(self, rownum=None):
        for col in self.columns:
            if rownum is None:
                yield col.format % col.name
            else:
                yield col.format % col.data[rownum]
    def get_line(self):
        for col in self.columns:
            yield '-' * (col.width + 2)
    def join_n_wrap(self, char, elements):
        return ' ' + char + char.join(elements) + char
    def get_rows(self):
        yield self.join_n_wrap('+', self.get_line())
        yield self.join_n_wrap('|', self.get_row(None))
        yield self.join_n_wrap('+', self.get_line())
        for rownum in range(0, self.length):
            yield self.join_n_wrap('|', self.get_row(rownum))
        yield self.join_n_wrap('+', self.get_line())
    def __str__(self):
        return '\n'.join(self.get_rows())
    class Column():
        LEFT, RIGHT = '-', ''
        def __init__(self, name, data, align=RIGHT):
            self.data = data
            self.name = name
            self.width = max(len(x) for x in data + [name])
            self.format = ' %%%s%ds ' % (align, self.width)

Column = Table.Column
nums = [ '1', '2', '3', '4' ]
speeds = [ '100', '10000', '1500', '12' ]
desc = [ '', 'label 1', 'none', 'very long description' ]
print Table(
    Column('NUM', nums),
    Column('SPEED', speeds),
    Column('DESC', desc, align=Column.LEFT))