How can I pretty-print ASCII tables with Python?
我正在寻找一种方法来漂亮地打印这样的表格:
1 2 3 4 5 6 | ======================= | column 1 | column 2 | ======================= | value1 | value2 | | value3 | value4 | ======================= |
我找到了asciitable库,但它不做边框等。我不需要任何复杂的数据项格式,它们只是字符串。我需要它来自动调整列的大小。
是否存在其他库或方法,或者我是否需要花费几分钟来编写自己的库或方法?
很久以前我就读过这个问题,并为表格写了自己漂亮的打印机:
我的用例是:
- 我大部分时间都想要一个单程航班
- 哪一个足够聪明,能为我找到最好的格式
- 可以输出不同的纯文本格式
例如,
1 2 3 4 5 6 7 8 9 | from tabulate import tabulate print tabulate([["value1","value2"], ["value3","value4"]], ["column 1","column 2"], tablefmt="grid") +------------+------------+ | column 1 | column 2 | +============+============+ | value1 | value2 | +------------+------------+ | value3 | value4 | +------------+------------+ |
其他支持的格式有:
从性能上看,
另外,我也很喜欢用十进制列来排列数字。所以这是数字的默认对齐方式(如果有)(可重写)。
这里有一个快速而肮脏的小函数,我编写它来显示我只能通过SOAP API进行的SQL查询的结果。它期望一个或多个
它对我来说很方便,可以作为你的起点:
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 | def pprinttable(rows): if len(rows) > 1: headers = rows[0]._fields lens = [] for i in range(len(rows[0])): lens.append(len(max([x[i] for x in rows] + [headers[i]],key=lambda x:len(str(x))))) formats = [] hformats = [] for i in range(len(rows[0])): if isinstance(rows[0][i], int): formats.append("%%%dd" % lens[i]) else: formats.append("%%-%ds" % lens[i]) hformats.append("%%-%ds" % lens[i]) pattern =" |".join(formats) hpattern =" |".join(hformats) separator ="-+-".join(['-' * n for n in lens]) print hpattern % tuple(headers) print separator _u = lambda t: t.decode('UTF-8', 'replace') if isinstance(t, str) else t for line in rows: print pattern % tuple(_u(t) for t in line) elif len(rows) == 1: row = rows[0] hwidth = len(max(row._fields,key=lambda x: len(x))) for i in range(len(row)): print"%*s = %s" % (hwidth,row._fields[i],row[i]) |
样品输出:
1 2 3 4 5 6 7 8 9 10 11 12 | pkid | fkn | npi -------------------------------------+--------------------------------------+---- 405fd665-0a2f-4f69-7320-be01201752ec | 8c9949b9-552e-e448-64e2-74292834c73e | 0 5b517507-2a42-ad2e-98dc-8c9ac6152afa | f972bee7-f5a4-8532-c4e5-2e82897b10f6 | 0 2f960dfc-b67a-26be-d1b3-9b105535e0a8 | ec3e1058-8840-c9f2-3b25-2488f8b3a8af | 1 c71b28a3-5299-7f4d-f27a-7ad8aeadafe0 | 72d25703-4735-310b-2e06-ff76af1e45ed | 0 3b0a5021-a52b-9ba0-1439-d5aafcf348e7 | d81bb78a-d984-e957-034d-87434acb4e97 | 1 96c36bb7-c4f4-2787-ada8-4aadc17d1123 | c171fe85-33e2-6481-0791-2922267e8777 | 1 95d0f85f-71da-bb9a-2d80-fe27f7c02fe2 | 226f964c-028d-d6de-bf6c-688d2908c5ae | 1 132aa774-42e5-3d3f-498b-50b44a89d401 | 44e31f89-d089-8afc-f4b1-ada051c01474 | 1 ff91641a-5802-be02-bece-79bca993fdbc | 33d8294a-053d-6ab4-94d4-890b47fcf70d | 1 f3196e15-5b61-e92d-e717-f00ed93fe8ae | 62fa4566-5ca2-4a36-f872-4d00f7abadcf | 1 |
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | >>> from collections import namedtuple >>> Row = namedtuple('Row',['first','second','third']) >>> data = Row(1,2,3) >>> data Row(first=1, second=2, third=3) >>> pprinttable([data]) first = 1 second = 2 third = 3 >>> pprinttable([data,data]) first | second | third ------+--------+------ 1 | 2 | 3 1 | 2 | 3 |
出于某种原因,当我在谷歌搜索中包含"docutils"时,我偶然发现了texttable,这似乎是我要找的。
我也写了自己的解决方案。我尽量保持简单。
https://github.com/robpol86/terminaltables网站
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 | from terminaltables import AsciiTable table_data = [ ['Heading1', 'Heading2'], ['row1 column1', 'row1 column2'], ['row2 column1', 'row2 column2'] ] table = AsciiTable(table_data) print table.table +--------------+--------------+ | Heading1 | Heading2 | +--------------+--------------+ | row1 column1 | row1 column2 | | row2 column1 | row2 column2 | +--------------+--------------+ table.inner_heading_row_border = False print table.table +--------------+--------------+ | Heading1 | Heading2 | | row1 column1 | row1 column2 | | row2 column1 | row2 column2 | +--------------+--------------+ table.inner_row_border = True table.justify_columns[1] = 'right' table.table_data[1][1] += ' newline' print table.table +--------------+--------------+ | Heading1 | Heading2 | +--------------+--------------+ | row1 column1 | row1 column2 | | | newline | +--------------+--------------+ | row2 column1 | row2 column2 | +--------------+--------------+ |
使用W3M设计用于处理类型Matth的版本接受:
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 | import subprocess import tempfile import html def pprinttable(rows): esc = lambda x: html.escape(str(x)) sour ="<table border=1>" if len(rows) == 1: for i in range(len(rows[0]._fields)): sour +="<tr><th>%s<td>%s" % (esc(rows[0]._fields[i]), esc(rows[0][i])) else: sour +="<tr>" +"".join(["<th>%s" % esc(x) for x in rows[0]._fields]) sour +="".join(["<tr>%s" %"".join(["<td>%s" % esc(y) for y in x]) for x in rows]) with tempfile.NamedTemporaryFile(suffix=".html") as f: f.write(sour.encode("utf-8")) f.flush() print( subprocess .Popen(["w3m","-dump",f.name], stdout=subprocess.PIPE) .communicate()[0].decode("utf-8").strip() ) from collections import namedtuple Row = namedtuple('Row',['first','second','third']) data1 = Row(1,2,3) data2 = Row(4,5,6) pprinttable([data1]) pprinttable([data1,data2]) |
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ┌───────┬─┐ │ first │1│ ├───────┼─┤ │second │2│ ├───────┼─┤ │ third │3│ └───────┴─┘ ┌─────┬───────┬─────┐ │first│second │third│ ├─────┼───────┼─────┤ │1 │2 │3 │ ├─────┼───────┼─────┤ │4 │5 │6 │ └─────┴───────┴─────┘ |
如果您想要一个具有列和行跨距的表,请尝试我的库dashtable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | from dashtable import data2rst table = [ ["Header 1","Header 2","Header3","Header 4"], ["row 1","column 2","column 3","column 4"], ["row 2","Cells span columns.","",""], ["row 3","Cells span rows.","- Cells - contain - blocks",""], ["row 4","","",""] ] # [Row, Column] pairs of merged cells span0 = ([2, 1], [2, 2], [2, 3]) span1 = ([3, 1], [4, 1]) span2 = ([3, 3], [3, 2], [4, 2], [4, 3]) my_spans = [span0, span1, span2] print(data2rst(table, spans=my_spans, use_headers=True)) |
输出:
1 2 3 4 5 6 7 8 9 10 11 | +----------+------------+----------+----------+ | Header 1 | Header 2 | Header3 | Header 4 | +==========+============+==========+==========+ | row 1 | column 2 | column 3 | column 4 | +----------+------------+----------+----------+ | row 2 | Cells span columns. | +----------+----------------------------------+ | row 3 | Cells | - Cells | +----------+ span rows. | - contain | | row 4 | | - blocks | +----------+------------+---------------------+ |
你可以试试漂亮的。它做你想做的。以下是它文档中的一个示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | >>> from beautifultable import BeautifulTable >>> table = BeautifulTable() >>> table.column_headers = ["name","rank","gender"] >>> table.append_row(["Jacob", 1,"boy"]) >>> table.append_row(["Isabella", 1,"girl"]) >>> table.append_row(["Ethan", 2,"boy"]) >>> table.append_row(["Sophia", 2,"girl"]) >>> table.append_row(["Michael", 3,"boy"]) >>> print(table) +----------+------+--------+ | name | rank | gender | +----------+------+--------+ | Jacob | 1 | boy | +----------+------+--------+ | Isabella | 1 | girl | +----------+------+--------+ | Ethan | 2 | boy | +----------+------+--------+ | Sophia | 2 | girl | +----------+------+--------+ | Michael | 3 | boy | +----------+------+--------+ |
我使用这个小的效用函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | def get_pretty_table(iterable, header): max_len = [len(x) for x in header] for row in iterable: row = [row] if type(row) not in (list, tuple) else row for index, col in enumerate(row): if max_len[index] < len(str(col)): max_len[index] = len(str(col)) output = '-' * (sum(max_len) + 1) + ' ' output += '|' + ''.join([h + ' ' * (l - len(h)) + '|' for h, l in zip(header, max_len)]) + ' ' output += '-' * (sum(max_len) + 1) + ' ' for row in iterable: row = [row] if type(row) not in (list, tuple) else row output += '|' + ''.join([str(c) + ' ' * (l - len(str(c))) + '|' for c, l in zip(row, max_len)]) + ' ' output += '-' * (sum(max_len) + 1) + ' ' return output print get_pretty_table([[1, 2], [3, 4]], ['header 1', 'header 2']) |
输出
1 2 3 4 5 6 | ----------------- |header 1|header 2| ----------------- |1 |2 | |3 |4 | ----------------- |
我知道这个问题有点老,但我的尝试是:
https://gist.github.com/lonetwin/4721748
它的imho可读性更高(尽管它不像@matth的解决方案那样区分单行/多行,也不使用namedtuples)。
我的解决方案是:
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 | def make_table(columns, data): """Create an ASCII table and return it as a string. Pass a list of strings to use as columns in the table and a list of dicts. The strings in 'columns' will be used as the keys to the dicts in 'data.' Not all column values have to be present in each data dict. >>> print(make_table(["a","b"], [{"a":"1","b":"test"}])) | a | b | |----------| | 1 | test | """ # Calculate how wide each cell needs to be cell_widths = {} for c in columns: values = [str(d.get(c,"")) for d in data] cell_widths[c] = len(max(values + [c])) # Used for formatting rows of data row_template ="|" +" {} |" * len(columns) # CONSTRUCT THE TABLE # The top row with the column titles justified_column_heads = [c.ljust(cell_widths[c]) for c in columns] header = row_template.format(*justified_column_heads) # The second row contains separators sep ="|" +"-" * (len(header) - 2) +"|" # Rows of data rows = [] for d in data: fields = [str(d.get(c,"")).ljust(cell_widths[c]) for c in columns] row = row_template.format(*fields) rows.append(row) return" ".join([header, sep] + rows) |
我刚刚发布了asciiplotlib,它也有漂亮的桌子。例如,这个
1 2 3 4 5 6 7 8 9 10 | import asciiplotlib as apl data = [ [["a","bb","ccc"]], [[1, 2, 3], [613.23236243236, 613.23236243236, 613.23236243236]], ] fig = apl.figure() fig.table(data, border_style="thin", ascii_mode=True, padding=(0, 1), alignment="lcr") fig.show() |
得到你
1 2 3 4 5 6 7 | +-----------------+-----------------+-----------------+ | a | bb | ccc | +=================+=================+=================+ | 1 | 2 | 3 | +-----------------+-----------------+-----------------+ | 613.23236243236 | 613.23236243236 | 613.23236243236 | +-----------------+-----------------+-----------------+ |
默认情况下,该表以Unicode制表符呈现,
1 2 3 4 5 6 7 | ┌─────────────────┬─────────────────┬─────────────────┐ │ a │ bb │ ccc │ ╞═════════════════╪═════════════════╪═════════════════╡ │ 1 │ 2 │ 3 │ ├─────────────────┼─────────────────┼─────────────────┤ │ 613.23236243236 │ 613.23236243236 │ 613.23236243236 │ └─────────────────┴─────────────────┴─────────────────┘ |
APL的表是非常可配置的;请查看测试以获取更多示例。
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 | from sys import stderr, stdout def create_table(table: dict, full_row: bool = False) -> None: min_len = len(min((v for v in table.values()), key=lambda q: len(q))) max_len = len(max((v for v in table.values()), key=lambda q: len(q))) if min_len < max_len: stderr.write("Table is out of shape, please make sure all columns have the same length.") stderr.flush() return additional_spacing = 1 heading_separator = '| ' horizontal_split = '| ' rc_separator = '' key_list = list(table.keys()) rc_len_values = [] for key in key_list: rc_len = len(max((v for v in table[key]), key=lambda q: len(str(q)))) rc_len_values += ([rc_len, [key]] for n in range(len(table[key]))) heading_line = (key + ("" * (rc_len + (additional_spacing + 1)))) + heading_separator stdout.write(heading_line) rc_separator += ("-" * (len(key) + (rc_len + (additional_spacing + 1)))) + '+-' if key is key_list[-1]: stdout.flush() stdout.write(' ' + rc_separator + ' ') value_list = [v for vl in table.values() for v in vl] aligned_data_offset = max_len row_count = len(key_list) next_idx = 0 newline_indicator = 0 iterations = 0 for n in range(len(value_list)): key = rc_len_values[next_idx][1][0] rc_len = rc_len_values[next_idx][0] line = ('{:{}} ' +"" * len(key)).format(value_list[next_idx], str(rc_len + additional_spacing)) + horizontal_split if next_idx >= (len(value_list) - aligned_data_offset): next_idx = iterations + 1 iterations += 1 else: next_idx += aligned_data_offset if newline_indicator >= row_count: if full_row: stdout.flush() stdout.write(' ' + rc_separator + ' ') else: stdout.flush() stdout.write(' ') newline_indicator = 0 stdout.write(line) newline_indicator += 1 stdout.write(' ' + rc_separator + ' ') stdout.flush() |
例子:
1 2 3 4 5 6 | table = { "uid": ["0","1","2","3"], "name": ["Jon","Doe","Lemma","Hemma"] } create_table(table) |
输出:
1 2 3 4 5 6 7 | uid | name | ------+------------+- 0 | Jon | 1 | Doe | 2 | Lemma | 3 | Hemma | ------+------------+- |
只有使用列表和字符串理解的非常紧凑的内置模块才能做到这一点。接受所有相同格式的词典列表…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | def tableit(dictlist): lengths = [ max(map(lambda x:len(x.get(k)), dictlist) + [len(k)]) for k in dictlist[0].keys() ] lenstr =" |".join("{:<%s}" % m for m in lengths) lenstr +=" " outmsg = lenstr.format(*dictlist[0].keys()) outmsg +="-" * (sum(lengths) + 3*len(lengths)) outmsg +=" " outmsg +="".join( lenstr.format(*v) for v in [ item.values() for item in dictlist ] ) return outmsg |