【问题标题】:How can I pretty-print ASCII tables with Python? [closed]如何使用 Python 漂亮地打印 ASCII 表? [关闭]
【发布时间】:2011-08-20 01:46:00
【问题描述】:

我正在寻找一种方法来漂亮地打印这样的表格:

=======================
| column 1 | column 2 |
=======================
| value1   | value2   |
| value3   | value4   |
=======================

我找到了asciitable 库,但它没有边框等。我不需要任何复杂的数据项格式,它们只是字符串。我确实需要它来自动调整列大小。

是否存在其他库或方法,或者我需要花几分钟自己编写?

【问题讨论】:

  • 为什么不使用 docutils 为你做这件事?
  • 什么叫桌子?表格中的数据是如何组织的? value1, value2, value3, value4... 是列表中的连续值吗?我觉得 fomat() 就够得到这么简单的显示了,不用学了很久的教程,解释如何通过使用库来获得时间
  • @korona:不,我没有提出建议。我在问一个问题。我不知道@kdt 知道或不知道什么。与其假设,我觉得不得不问。
  • 在我看来,您实际上是在假设他知道 docutils。也许他没有?
  • @S.Lott 我看过 docutils,虽然它当然非常适合将文本转换为 html、latex 等,但我看不到 generate i> 漂亮的文本表,列排列整齐,使用固定宽度字体看起来很漂亮。你是不是误解了 kdt 的目标,还是我遗漏了什么?

标签: python ascii tabular


【解决方案1】:

你可以试试BeautifulTable。它做你想做的事。这是documentation的一个例子

>>> from beautifultable import BeautifulTable
>>> table = BeautifulTable()
>>> table.columns.header = ["name", "rank", "gender"]
>>> table.rows.append(["Jacob", 1, "boy"])
>>> table.rows.append(["Isabella", 1, "girl"])
>>> table.rows.append(["Ethan", 2, "boy"])
>>> table.rows.append(["Sophia", 2, "girl"])
>>> table.rows.append(["Michael", 3, "boy"])
>>> print(table)
+----------+------+--------+
|   name   | rank | gender |
+----------+------+--------+
|  Jacob   |  1   |  boy   |
+----------+------+--------+
| Isabella |  1   |  girl  |
+----------+------+--------+
|  Ethan   |  2   |  boy   |
+----------+------+--------+
|  Sophia  |  2   |  girl  |
+----------+------+--------+
| Michael  |  3   |  boy   |
+----------+------+--------+

【讨论】:

  • /usr/local/lib/python3.8/site-packages/beautifultable/utils.py:113: FutureWarning: 'BeautifulTable.column_headers' has been deprecated in 'v1.0.0' and will be removed in 'v1.2.0'. Use 'BTColumnCollection.header' instead. warnings.warn(message, FutureWarning)
  • /usr/local/lib/python3.8/site-packages/beautifultable/utils.py:113: FutureWarning: 'BeautifulTable.append_row' has been deprecated in 'v1.0.0' and will be removed in 'v1.2.0'. Use 'BTRowCollection.append' instead. warnings.warn(message, FutureWarning)
【解决方案2】:

我刚刚为此发布了termtables。比如这个

import termtables as tt

tt.print(
    [[1, 2, 3], [613.23236243236, 613.23236243236, 613.23236243236]],
    header=["a", "bb", "ccc"],
    style=tt.styles.ascii_thin_double,
    padding=(0, 1),
    alignment="lcr"
)

得到你

+-----------------+-----------------+-----------------+
| a               |       bb        |             ccc |
+=================+=================+=================+
| 1               |        2        |               3 |
+-----------------+-----------------+-----------------+
| 613.23236243236 | 613.23236243236 | 613.23236243236 |
+-----------------+-----------------+-----------------+

默认情况下,表格以 Unicode box-drawing characters 呈现,

┌─────────────────┬─────────────────┬─────────────────┐
│ a               │       bb        │             ccc │
╞═════════════════╪═════════════════╪═════════════════╡
│ 1               │        2        │               3 │
├─────────────────┼─────────────────┼─────────────────┤
│ 613.23236243236 │ 613.23236243236 │ 613.23236243236 │
└─────────────────┴─────────────────┴─────────────────┘

术语表是非常可配置的;查看the tests 了解更多示例。

【讨论】:

  • 我希望您可以设置要显示的最大列并让库处理包装逻辑。
【解决方案3】:

由于某种原因,当我在我的谷歌搜索中包含“docutils”时,我偶然发现了texttable,这似乎是我正在寻找的。​​p>

【讨论】:

【解决方案4】:
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('\n' + rc_separator + '\n')

        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('\n' + rc_separator + '\n')
                else:
                    stdout.flush()
                    stdout.write('\n')

                newline_indicator = 0

            stdout.write(line)
            newline_indicator += 1

        stdout.write('\n' + rc_separator + '\n')
        stdout.flush()

例子:

table = {
        "uid": ["0", "1", "2", "3"],
        "name": ["Jon", "Doe", "Lemma", "Hemma"]
    }

create_table(table)

输出:

uid   | name       | 
------+------------+-
0     | Jon        | 
1     | Doe        | 
2     | Lemma      | 
3     | Hemma      | 
------+------------+-

【讨论】:

  • 您可以通过增加一些解释来改进您的纯代码答案。
【解决方案5】:

这可以通过使用列表和字符串推导相当紧凑的内置模块来完成。接受所有相同格式的字典列表...

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 += "\n"

    outmsg = lenstr.format(*dictlist[0].keys())
    outmsg += "-" * (sum(lengths) + 3*len(lengths))
    outmsg += "\n"
    outmsg += "".join(
        lenstr.format(*v) for v in [ item.values() for item in dictlist ]
    )
    return outmsg

【讨论】:

  • 请提交一个如何使用您的代码的示例。 tableit([dict(a='1', b='2', c='3'), dict(a='x', b='y', c='z')])
  • 如果不更改 python 3,您的代码将无法工作,第 2 行必须是:lengths = [ max(list(map(lambda x:len(x.get(k)), dictlist)) + [len(k)]) for k in dictlist[0].keys() ]
【解决方案6】:

如果您想要一个具有列和行跨度的表格,请尝试我的库dashtable

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\nspan rows.", "- Cells\n- contain\n- 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))

哪些输出:

+----------+------------+----------+----------+
| 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            |
+----------+------------+---------------------+

【讨论】:

  • ERROR: Spans must be a list of lists
【解决方案7】:

我使用这个小实用功能。

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) + '\n'
    output += '|' + ''.join([h + ' ' * (l - len(h)) + '|' for h, l in zip(header, max_len)]) + '\n'
    output += '-' * (sum(max_len) + 1) + '\n'
    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)]) + '\n'
    output += '-' * (sum(max_len) + 1) + '\n'
    return output

print get_pretty_table([[1, 2], [3, 4]], ['header 1', 'header 2'])

输出

-----------------
|header 1|header 2|
-----------------
|1       |2       |
|3       |4       |
-----------------

【讨论】:

  • 您在output += '|' + ''.join([h + ' ' * (l - len(h)) + '|' for h, l in zip(header, max_len)]) + '\n' 的每列之间添加了一个空格,但不在分隔线中。可以使用像output = '-' * (sum(max_len) + 1 + len(header)) + '\n' 这样简单的东西来扩展-s 的那一行
【解决方案8】:

这是我的解决方案:

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 "\n".join([header, sep] + rows)

【讨论】:

    【解决方案9】:

    这是我编写的一个快速而肮脏的小函数,用于显示我只能通过 SOAP API 进行的 SQL 查询的结果。它期望输入一个或多个namedtuples 的序列作为表行。如果只有一条记录,它会以不同的方式打印出来。

    这对我来说很方便,可以作为你的起点:

    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])
    

    样本输出:

    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
    

    例子

    >>> 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
    

    【讨论】:

    • @MattH 你能举例说明这个函数的用法吗?
    • @MattH 谢谢,但大数字似乎马上就崩溃了。 TypeError:“int”类型的对象没有 len()。
    • @Alborz:我将其发布为其他人的起点,如果您愿意,可以对其进行自定义以处理您的数据类型。虽然取决于错误来自哪一行,但您可能没有按预期调用函数
    • @theAlse 我修复了您发现的错误,方法是在 lens.append 行中将其设为 len(str(max(...)))。所以现在如果列中的数字比列标题宽,我们仍然很好。顺便说一句,MattH - 对 max() 的“key”参数的可爱使用!
    【解决方案10】:

    我也为此编写了自己的解决方案。我尽量保持简单。

    https://github.com/Robpol86/terminaltables

    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] += '\nnewline'
    print table.table
    +--------------+--------------+
    | Heading1     |     Heading2 |
    +--------------+--------------+
    | row1 column1 | row1 column2 |
    |              |      newline |
    +--------------+--------------+
    | row2 column1 | row2 column2 |
    +--------------+--------------+
    

    【讨论】:

    • 这个项目似乎死了
    【解决方案11】:

    我很久以前就读过这个问题,并完成了我自己的漂亮的表格打印机:tabulate

    我的用例是:

    • 我大部分时间都想要单线
    • 它足够聪明,可以为我找出最佳格式
    • 并且可以输出不同的纯文本格式

    以您的示例为例,grid 可能是最相似的输出格式:

    from tabulate import tabulate
    print tabulate([["value1", "value2"], ["value3", "value4"]], ["column 1", "column 2"], tablefmt="grid")
    +------------+------------+
    | column 1   | column 2   |
    +============+============+
    | value1     | value2     |
    +------------+------------+
    | value3     | value4     |
    +------------+------------+
    

    其他支持的格式有plain(无行)、simple(Pandoc 简单表格)、pipe(如 PHP Markdown Extra 中的表格)、orgtbl(如 Emacs 组织模式中的表格)、 rst(就像 reStructuredText 中的简单表格)。 gridorgtbl 在 Emacs 中很容易编辑。

    在性能方面,tabulateasciitable 稍慢,但比PrettyTabletexttable 快得多。

    附:我也是对齐数字by a decimal column 的忠实粉丝。所以这是数字的默认对齐方式(如果有的话)(可覆盖)。

    【讨论】:

    • 我只是碰巧需要一个制表解决方案,有幸找到您的图书馆!像魅力一样工作 :D 如果你在听,只想说 谢谢 :)
    • 是的,我在听。谢谢你的客气话。能得到积极的反馈真是太好了。
    • 嗨,@sastinin 首先,非常感谢您提供这么好的图书馆。我可以知道是否有任何选项可以打印表格以跨越终端的整个宽度?
    • 您好 sastinin,只是想在这里说一句话,感谢您提供这个非常方便的软件包。像魅力一样工作,让我省去了自己写的麻烦。非常感谢分享!
    • 您的功能列表是轻描淡写的。尝试过 ansi 逃脱的东西,效果很好。谢谢!
    【解决方案12】:

    我知道这个问题有点老了,但这是我的尝试:

    https://gist.github.com/lonetwin/4721748

    恕我直言,它更具可读性(尽管它不像 @MattH 的解决方案那样区分单行/多行,也不使用 NamedTuples)。

    【讨论】:

      【解决方案13】:

      使用 w3m 的版本旨在处理 MattH 的版本接受的类型:

      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])
      

      结果:

      ┌───────┬─┐
      │ first │1│
      ├───────┼─┤
      │second │2│
      ├───────┼─┤
      │ third │3│
      └───────┴─┘
      ┌─────┬───────┬─────┐
      │first│second │third│
      ├─────┼───────┼─────┤
      │1    │2      │3    │
      ├─────┼───────┼─────┤
      │4    │5      │6    │
      └─────┴───────┴─────┘
      

      【讨论】:

        猜你喜欢
        • 2020-10-23
        • 2015-02-07
        • 1970-01-01
        • 1970-01-01
        • 2013-10-03
        • 2011-08-05
        • 2010-12-26
        相关资源
        最近更新 更多