Create good column output in python

I am trying to create a nice list of columns in python for use with the command line administration tools that I create.

Basically, I need a list, for example:

[['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']] 

To turn on:

 abc aaaaaaaaaa bc a bbbbbbbbbb c 

Using simple tabs will not do the trick here, because I do not know the longest data in each row.

This is the same behavior as "column -t" on Linux ..

 $ echo -e "abc\naaaaaaaaaa bc\na bbbbbbbbbb c" abc aaaaaaaaaa bc a bbbbbbbbbb c $ echo -e "abc\naaaaaaaaaa bc\na bbbbbbbbbb c" | column -t abc aaaaaaaaaa bc a bbbbbbbbbb c 

I have looked at various python libraries for this, but cannot find anything useful.

+94
python string list
Apr 3 '12 at 8:00
source share
13 answers
 data = [['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']] col_width = max(len(word) for row in data for word in row) + 2 # padding for row in data: print "".join(word.ljust(col_width) for word in row) abc aaaaaaaaaa bca bbbbbbbbbb c 

What it is, calculates the longest data entry to determine the width of a column, and then use .ljust() to add the required padding when printing each column.

+110
Apr 3 '12 at 8:10
source share

Starting with Python 2.6+, you can use the format string as follows to set the columns to at least 20 characters and align the text to the right.

 table_data = [ ['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c'] ] for row in table_data: print("{: >20} {: >20} {: >20}".format(*row)) 

Exit:

  abc aaaaaaaaaa bc a bbbbbbbbbb c 
+117
Apr 03 2018-12-12T00:
source share

I came here with the same requirements, but @lvc and @Preet answers look more stringent with what column -t produces in that the columns have different widths:

 >>> rows = [ ['a', 'b', 'c', 'd'] ... , ['aaaaaaaaaa', 'b', 'c', 'd'] ... , ['a', 'bbbbbbbbbb', 'c', 'd'] ... ] ... 



 >>> widths = [max(map(len, col)) for col in zip(*rows)] >>> for row in rows: ... print " ".join((val.ljust(width) for val, width in zip(row, widths))) ... abcd aaaaaaaaaa bcd a bbbbbbbbbb cd 
+40
Aug 22 2018-12-12T00:
source share

You must do this with two passes:

  • get the maximum width of each column.
  • formatting columns using our knowledge of the maximum width from the first pass with str.ljust() and str.rjust()
+8
Apr 03 2018-12-12T00:
source share

Transposing columns is like a job for zip:

 >>> a = [['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']] >>> list(zip(*a)) [('a', 'aaaaaaaaaa', 'a'), ('b', 'b', 'bbbbbbbbbb'), ('c', 'c', 'c')] 

To find the desired length of each column, you can use max :

 >>> trans_a = zip(*a) >>> [max(len(c) for c in b) for b in trans_a] [10, 10, 1] 

What you can use with a suitable add-on for building strings for going to print :

 >>> col_lenghts = [max(len(c) for c in b) for b in trans_a] >>> padding = ' ' # You might want more >>> padding.join(s.ljust(l) for s,l in zip(a[0], col_lenghts)) 'abc' 
+7
Apr 3 '12 at 8:16
source share

To get more beautiful tables like

 --------------------------------------------------- | First Name | Last Name | Age | Position | --------------------------------------------------- | John | Smith | 24 | Software | | | | | Engineer | --------------------------------------------------- | Mary | Brohowski | 23 | Sales | | | | | Manager | --------------------------------------------------- | Aristidis | Papageorgopoulos | 28 | Senior | | | | | Reseacher | --------------------------------------------------- 

You can use this Python recipe :

 ''' From http://code.activestate.com/recipes/267662-table-indentation/ PSF License ''' import cStringIO,operator def indent(rows, hasHeader=False, headerChar='-', delim=' | ', justify='left', separateRows=False, prefix='', postfix='', wrapfunc=lambda x:x): """Indents a table by column. - rows: A sequence of sequences of items, one sequence per row. - hasHeader: True if the first row consists of the columns' names. - headerChar: Character to be used for the row separator line (if hasHeader==True or separateRows==True). - delim: The column delimiter. - justify: Determines how are data justified in their column. Valid values are 'left','right' and 'center'. - separateRows: True if rows are to be separated by a line of 'headerChar's. - prefix: A string prepended to each printed row. - postfix: A string appended to each printed row. - wrapfunc: A function f(text) for wrapping text; each element in the table is first wrapped by this function.""" # closure for breaking logical rows to physical, using wrapfunc def rowWrapper(row): newRows = [wrapfunc(item).split('\n') for item in row] return [[substr or '' for substr in item] for item in map(None,*newRows)] # break each logical row into one or more physical ones logicalRows = [rowWrapper(row) for row in rows] # columns of physical rows columns = map(None,*reduce(operator.add,logicalRows)) # get the maximum of each column by the string length of its items maxWidths = [max([len(str(item)) for item in column]) for column in columns] rowSeparator = headerChar * (len(prefix) + len(postfix) + sum(maxWidths) + \ len(delim)*(len(maxWidths)-1)) # select the appropriate justify method justify = {'center':str.center, 'right':str.rjust, 'left':str.ljust}[justify.lower()] output=cStringIO.StringIO() if separateRows: print >> output, rowSeparator for physicalRows in logicalRows: for row in physicalRows: print >> output, \ prefix \ + delim.join([justify(str(item),width) for (item,width) in zip(row,maxWidths)]) \ + postfix if separateRows or hasHeader: print >> output, rowSeparator; hasHeader=False return output.getvalue() # written by Mike Brown # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061 def wrap_onspace(text, width): """ A word-wrap function that preserves existing line breaks and most spaces in the text. Expects that existing line breaks are posix newlines (\n). """ return reduce(lambda line, word, width=width: '%s%s%s' % (line, ' \n'[(len(line[line.rfind('\n')+1:]) + len(word.split('\n',1)[0] ) >= width)], word), text.split(' ') ) import re def wrap_onspace_strict(text, width): """Similar to wrap_onspace, but enforces the width constraint: words longer than width are split.""" wordRegex = re.compile(r'\S{'+str(width)+r',}') return wrap_onspace(wordRegex.sub(lambda m: wrap_always(m.group(),width),text),width) import math def wrap_always(text, width): """A simple word-wrap function that wraps text on exactly width characters. It doesn't split the text in words.""" return '\n'.join([ text[width*i:width*(i+1)] \ for i in xrange(int(math.ceil(1.*len(text)/width))) ]) if __name__ == '__main__': labels = ('First Name', 'Last Name', 'Age', 'Position') data = \ '''John,Smith,24,Software Engineer Mary,Brohowski,23,Sales Manager Aristidis,Papageorgopoulos,28,Senior Reseacher''' rows = [row.strip().split(',') for row in data.splitlines()] print 'Without wrapping function\n' print indent([labels]+rows, hasHeader=True) # test indent with different wrapping functions width = 10 for wrapper in (wrap_always,wrap_onspace,wrap_onspace_strict): print 'Wrapping function: %s(x,width=%d)\n' % (wrapper.__name__,width) print indent([labels]+rows, hasHeader=True, separateRows=True, prefix='| ', postfix=' |', wrapfunc=lambda x: wrapper(x,width)) # output: # #Without wrapping function # #First Name | Last Name | Age | Position #------------------------------------------------------- #John | Smith | 24 | Software Engineer #Mary | Brohowski | 23 | Sales Manager #Aristidis | Papageorgopoulos | 28 | Senior Reseacher # #Wrapping function: wrap_always(x,width=10) # #---------------------------------------------- #| First Name | Last Name | Age | Position | #---------------------------------------------- #| John | Smith | 24 | Software E | #| | | | ngineer | #---------------------------------------------- #| Mary | Brohowski | 23 | Sales Mana | #| | | | ger | #---------------------------------------------- #| Aristidis | Papageorgo | 28 | Senior Res | #| | poulos | | eacher | #---------------------------------------------- # #Wrapping function: wrap_onspace(x,width=10) # #--------------------------------------------------- #| First Name | Last Name | Age | Position | #--------------------------------------------------- #| John | Smith | 24 | Software | #| | | | Engineer | #--------------------------------------------------- #| Mary | Brohowski | 23 | Sales | #| | | | Manager | #--------------------------------------------------- #| Aristidis | Papageorgopoulos | 28 | Senior | #| | | | Reseacher | #--------------------------------------------------- # #Wrapping function: wrap_onspace_strict(x,width=10) # #--------------------------------------------- #| First Name | Last Name | Age | Position | #--------------------------------------------- #| John | Smith | 24 | Software | #| | | | Engineer | #--------------------------------------------- #| Mary | Brohowski | 23 | Sales | #| | | | Manager | #--------------------------------------------- #| Aristidis | Papageorgo | 28 | Senior | #| | poulos | | Reseacher | #--------------------------------------------- 

The Python recipe page contains several enhancements.

+7
Aug 13 '15 at
source share

pandas data frame creation solution:

 import pandas as pd l = [['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']] df = pd.DataFrame(l) print(df) 0 1 2 0 abc 1 aaaaaaaaaa bc 2 a bbbbbbbbbb c 

To remove index and header values ​​to create output, you want to use the to_string method:

 result = df.to_string(index=False, header=False) print(result) abc aaaaaaaaaa bc a bbbbbbbbbb c 
+5
Dec 30 '15 at 6:47
source share

It's a bit late for the party, and a shameless plugin for the package I wrote, but you can also check out the Columnar package.

It takes a list of input lists and a list of headers and displays a string in table format. This snippet creates a docker-esque table:

 from columnar import columnar headers = ['name', 'id', 'host', 'notes'] data = [ ['busybox', 'c3c37d5d-38d2-409f-8d02-600fd9d51239', 'linuxnode-1-292735', 'Test server.'], ['alpine-python', '6bb77855-0fda-45a9-b553-e19e1a795f1e', 'linuxnode-2-249253', 'The one that runs python.'], ['redis', 'afb648ba-ac97-4fb2-8953-9a5b5f39663e', 'linuxnode-3-3416918', 'For queues and stuff.'], ['app-server', 'b866cd0f-bf80-40c7-84e3-c40891ec68f9', 'linuxnode-4-295918', 'A popular destination.'], ['nginx', '76fea0f0-aa53-4911-b7e4-fae28c2e469b', 'linuxnode-5-292735', 'Traffic Cop'], ] table = columnar(data, headers, no_borders=True) print(table) 

Table Displaying No-border Style

Or you can become a little more attractive with colors and borders. Table Displaying Spring Classics

To learn more about the column sizing algorithm and see the rest of the API, you can check the link above or see the Columnar GitHub Repo

+3
Apr 15 '19 at 21:42
source share

I understand that this question is old, but I did not understand Antak's answer and did not want to use the library, so I made my own decision.

The solution assumes that the records are a 2D array, the records are the same length and that all fields are strings.

 def stringifyRecords(records): column_widths = [0] * len(records[0]) for record in records: for i, field in enumerate(record): width = len(field) if width > column_widths[i]: column_widths[i] = width s = "" for record in records: for column_width, field in zip(column_widths, record): s += field.ljust(column_width+1) s += "\n" return s 
0
Feb 08 '17 at 17:45
source share

I found this answer very useful and elegant, originally from here :

 matrix = [["A", "B"], ["C", "D"]] print('\n'.join(['\t'.join([str(cell) for cell in row]) for row in matrix])) 

Exit

 AB CD 
0
Jul 10 '18 at 14:52
source share

Here is an answer from Sean Chin. The width is fixed for each column, not for all columns. There is also a border below the first row and between the columns. (The icontract library is used to enforce contracts.)

 @icontract.pre( lambda table: not table or all(len(row) == len(table[0]) for row in table)) @icontract.post(lambda table, result: result == "" if not table else True) @icontract.post(lambda result: not result.endswith("\n")) def format_table(table: List[List[str]]) -> str: """ Format the table as equal-spaced columns. :param table: rows of cells :return: table as string """ cols = len(table[0]) col_widths = [max(len(row[i]) for row in table) for i in range(cols)] lines = [] # type: List[str] for i, row in enumerate(table): parts = [] # type: List[str] for cell, width in zip(row, col_widths): parts.append(cell.ljust(width)) line = " | ".join(parts) lines.append(line) if i == 0: border = [] # type: List[str] for width in col_widths: border.append("-" * width) lines.append("-+-".join(border)) result = "\n".join(lines) return result 

Here is an example:

 >>> table = [['column 0', 'another column 1'], ['00', '01'], ['10', '11']] >>> result = packagery._format_table(table=table) >>> print(result) column 0 | another column 1 ---------+----------------- 00 | 01 10 | 11 
0
Sep 18 '18 at 5:43
source share

Scolp is a new library that makes it easy to print streaming column data by automatically adjusting the column width.

(Disclaimer: I am the author)

0
Jun 06 '19 at 10:30
source share

@Franck Dernoncourt original recipe updated to meet Python 3 and PEP8 requirements

 import io import math import operator import re import functools from itertools import zip_longest def indent( rows, has_header=False, header_char="-", delim=" | ", justify="left", separate_rows=False, prefix="", postfix="", wrapfunc=lambda x: x, ): """Indents a table by column. - rows: A sequence of sequences of items, one sequence per row. - hasHeader: True if the first row consists of the columns' names. - headerChar: Character to be used for the row separator line (if hasHeader==True or separateRows==True). - delim: The column delimiter. - justify: Determines how are data justified in their column. Valid values are 'left','right' and 'center'. - separateRows: True if rows are to be separated by a line of 'headerChar's. - prefix: A string prepended to each printed row. - postfix: A string appended to each printed row. - wrapfunc: A function f(text) for wrapping text; each element in the table is first wrapped by this function.""" # closure for breaking logical rows to physical, using wrapfunc def row_wrapper(row): new_rows = [wrapfunc(item).split("\n") for item in row] return [[substr or "" for substr in item] for item in zip_longest(*new_rows)] # break each logical row into one or more physical ones logical_rows = [row_wrapper(row) for row in rows] # columns of physical rows columns = zip_longest(*functools.reduce(operator.add, logical_rows)) # get the maximum of each column by the string length of its items max_widths = [max([len(str(item)) for item in column]) for column in columns] row_separator = header_char * ( len(prefix) + len(postfix) + sum(max_widths) + len(delim) * (len(max_widths) - 1) ) # select the appropriate justify method justify = {"center": str.center, "right": str.rjust, "left": str.ljust}[ justify.lower() ] output = io.StringIO() if separate_rows: print(output, row_separator) for physicalRows in logical_rows: for row in physicalRows: print( output, prefix + delim.join( [justify(str(item), width) for (item, width) in zip(row, max_widths)] ) + postfix) if separate_rows or has_header: print(output, row_separator) has_header = False return output.getvalue() # written by Mike Brown # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061 def wrap_onspace(text, width): """ A word-wrap function that preserves existing line breaks and most spaces in the text. Expects that existing line breaks are posix newlines (\n). """ return functools.reduce( lambda line, word, i_width=width: "%s%s%s" % ( line, " \n"[ ( len(line[line.rfind("\n") + 1 :]) + len(word.split("\n", 1)[0]) >= i_width ) ], word, ), text.split(" "), ) def wrap_onspace_strict(text, i_width): """Similar to wrap_onspace, but enforces the width constraint: words longer than width are split.""" word_regex = re.compile(r"\S{" + str(i_width) + r",}") return wrap_onspace( word_regex.sub(lambda m: wrap_always(m.group(), i_width), text), i_width ) def wrap_always(text, width): """A simple word-wrap function that wraps text on exactly width characters. It doesn't split the text in words.""" return "\n".join( [ text[width * i : width * (i + 1)] for i in range(int(math.ceil(1.0 * len(text) / width))) ] ) if __name__ == "__main__": labels = ("First Name", "Last Name", "Age", "Position") data = """John,Smith,24,Software Engineer Mary,Brohowski,23,Sales Manager Aristidis,Papageorgopoulos,28,Senior Reseacher""" rows = [row.strip().split(",") for row in data.splitlines()] print("Without wrapping function\n") print(indent([labels] + rows, has_header=True)) # test indent with different wrapping functions width = 10 for wrapper in (wrap_always, wrap_onspace, wrap_onspace_strict): print("Wrapping function: %s(x,width=%d)\n" % (wrapper.__name__, width)) print( indent( [labels] + rows, has_header=True, separate_rows=True, prefix="| ", postfix=" |", wrapfunc=lambda x: wrapper(x, width), ) ) # output: # # Without wrapping function # # First Name | Last Name | Age | Position # ------------------------------------------------------- # John | Smith | 24 | Software Engineer # Mary | Brohowski | 23 | Sales Manager # Aristidis | Papageorgopoulos | 28 | Senior Reseacher # # Wrapping function: wrap_always(x,width=10) # # ---------------------------------------------- # | First Name | Last Name | Age | Position | # ---------------------------------------------- # | John | Smith | 24 | Software E | # | | | | ngineer | # ---------------------------------------------- # | Mary | Brohowski | 23 | Sales Mana | # | | | | ger | # ---------------------------------------------- # | Aristidis | Papageorgo | 28 | Senior Res | # | | poulos | | eacher | # ---------------------------------------------- # # Wrapping function: wrap_onspace(x,width=10) # # --------------------------------------------------- # | First Name | Last Name | Age | Position | # --------------------------------------------------- # | John | Smith | 24 | Software | # | | | | Engineer | # --------------------------------------------------- # | Mary | Brohowski | 23 | Sales | # | | | | Manager | # --------------------------------------------------- # | Aristidis | Papageorgopoulos | 28 | Senior | # | | | | Reseacher | # --------------------------------------------------- # # Wrapping function: wrap_onspace_strict(x,width=10) # # --------------------------------------------- # | First Name | Last Name | Age | Position | # --------------------------------------------- # | John | Smith | 24 | Software | # | | | | Engineer | # --------------------------------------------- # | Mary | Brohowski | 23 | Sales | # | | | | Manager | # --------------------------------------------- # | Aristidis | Papageorgo | 28 | Senior | # | | poulos | | Reseacher | # --------------------------------------------- 
0
Jun 21 '19 at 16:40
source share



All Articles