Create nice column output in python
我试图在python中创建一个很好的列列表,用于我创建的命令行管理工具。
基本上,我想要一个列表,比如:
1 | [['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']] |
变成:
1 2 3 | a b c aaaaaaaaaa b c a bbbbbbbbbb c |
使用简单的制表符在这里不起作用,因为我不知道每行中最长的数据。
这与Linux中的"column-t"行为相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 | $ echo -e"a b c aaaaaaaaaa b c a bbbbbbbbbb c" a b c aaaaaaaaaa b c a bbbbbbbbbb c $ echo -e"a b c aaaaaaaaaa b c a bbbbbbbbbb c" | column -t a b c aaaaaaaaaa b c a bbbbbbbbbb c |
我到处寻找各种各样的python库来完成这项工作,但是找不到任何有用的。
由于python 2.6+,您可以使用以下格式字符串将列设置为至少20个字符,并将文本右对齐。
1 2 3 4 5 6 7 | table_data = [ ['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c'] ] for row in table_data: print("{: >20} {: >20} {: >20}".format(*row)) |
输出:
1 2 3 | a b c aaaaaaaaaa b c a bbbbbbbbbb c |
1 2 3 4 5 6 7 8 9 | 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) a b c aaaaaaaaaa b c a bbbbbbbbbb c |
这样做的目的是计算最长的数据条目以确定列宽,然后在打印出每一列时使用
我来这里的时候也有同样的要求,但是@lvc和@preet的答案似乎更符合
1 2 3 4 5 | >>> rows = [ ['a', 'b', 'c', 'd'] ... , ['aaaaaaaaaa', 'b', 'c', 'd'] ... , ['a', 'bbbbbbbbbb', 'c', 'd'] ... ] ... |
1 2 3 4 5 6 7 | >>> 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))) ... a b c d aaaaaaaaaa b c d a bbbbbbbbbb c d |
你必须通过两次传球:
得到更豪华的桌子
1 2 3 4 5 6 7 8 9 10 11 12 | --------------------------------------------------- | First Name | Last Name | Age | Position | --------------------------------------------------- | John | Smith | 24 | Software | | | | | Engineer | --------------------------------------------------- | Mary | Brohowski | 23 | Sales | | | | | Manager | --------------------------------------------------- | Aristidis | Papageorgopoulos | 28 | Senior | | | | | Reseacher | --------------------------------------------------- |
您可以使用这个python配方:
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 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 | ''' 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(' ') 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 ( ). """ return reduce(lambda line, word, width=width: '%s%s%s' % (line, ' '[(len(line[line.rfind(' ')+1:]) + len(word.split(' ',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 ' '.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 ' 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) ' % (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 | #--------------------------------------------- |
python配方页面包含一些改进。
像这样转换列是zip的一项工作:
1 2 3 | >>> a = [['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']] >>> list(zip(*a)) [('a', 'aaaaaaaaaa', 'a'), ('b', 'b', 'bbbbbbbbbb'), ('c', 'c', 'c')] |
要找到每列所需的长度,可以使用
1 2 3 | >>> trans_a = zip(*a) >>> [max(len(c) for c in b) for b in trans_a] [10, 10, 1] |
它可以使用适当的填充来构造要传递给
1 2 3 4 | >>> 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)) 'a b c' |
基于
1 2 3 4 5 6 7 8 9 | import pandas as pd l = [['a', 'b', 'c'], ['aaaaaaaaaa', 'b', 'c'], ['a', 'bbbbbbbbbb', 'c']] df = pd.DataFrame(l) print(df) 0 1 2 0 a b c 1 aaaaaaaaaa b c 2 a bbbbbbbbbb c |
要删除索引和标题值以创建所需的输出,可以使用
1 2 3 4 5 6 | result = df.to_string(index=False, header=False) print(result) a b c aaaaaaaaaa b c a bbbbbbbbbb c |
这对派对来说有点晚了,对于我写的一个包来说是一个无耻的插件,但是你也可以查看这个专栏包。
它获取输入列表和头列表,并输出一个表格式的字符串。这段代码创建了一个Docker风格的表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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) |
或者你可以对颜色和边框有点着迷。
要了解有关列大小调整算法的更多信息并查看API的其余部分,您可以查看上面的链接或查看列Github repo
这是肖恩·钦回答的一个变化。每列的宽度是固定的,不是所有列的宽度。第一行下面和列之间也有边框。(IContract库用于执行合同。)
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 | @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(" ")) 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 =" ".join(lines) return result |
下面是一个例子:
1 2 3 4 5 6 7 | >>> 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 |
我发现这个答案非常有用而且优雅,最初是从这里开始的:
1 2 3 4 | matrix = [["A","B"], ["C","D"]] print(' '.join(['\t'.join([str(cell) for cell in row]) for row in matrix])) |
产量
1 2 | A B C D |
我知道这个问题很古老,但我不理解安塔克的答案,也不想使用图书馆,所以我推出了自己的解决方案。
解决方案假定记录是二维数组,记录的长度都相同,字段都是字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 +=" " return s |