Как я могу красиво распечатать таблицы ASCII с помощью Python? [закрыто]

81

Я ищу способ красиво распечатать такие таблицы:

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

Я нашел аскетичный библиотеку но она не делает границ и т.д. Мне не нужно сложное форматирование элементов данных, это просто строки. Мне это нужно для автоматического изменения размера столбцов.

Существуют ли другие библиотеки или методы, или мне нужно потратить несколько минут на написание собственных?

kdt
источник
Почему бы не использовать для этого Docutils?
S.Lott
Как вы называете стол? Как данные организованы в таблице? Являются ли значение1, значение2, значение3, значение4 ... последовательными значениями в списке? Я думаю, что fomat () достаточно, чтобы получить такое простое отображение, не нужно долго изучать учебник, в котором объясняется, как выиграть время с помощью библиотеки
eyquem
2
@korona: Нет, я не предлагал. Я задавал вопрос. Я понятия не имею, что знает или не знает @kdt. Вместо того, чтобы предполагать, я чувствую себя обязанным спросить.
S.Lott
5
Мне показалось, что вы на самом деле предполагали, что он знает о Docutils. Может, нет?
корона
2
@ S.Lott Я просмотрел документацию, и хотя она, конечно, отлично подходит для преобразования текста в html, latex и т. Д., Я не вижу способа создавать красивые текстовые таблицы с столбцами, которые выстраиваются в линию и выглядят красиво с шрифты фиксированной ширины. Вы неправильно поняли цель kdt, или я что-то упустил?
nealmcb

Ответы:

72

Я читал этот вопрос давно, и закончил писать свой собственный довольно-принтер для таблиц: 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). gridи orgtblих легко редактировать в Emacs.

С точки зрения tabulateпроизводительности немного медленнее asciitable, но намного быстрее, чем PrettyTableи texttable.

PS Еще я большой поклонник выравнивания чисел по десятичному столбцу . Так что это выравнивание по умолчанию для чисел, если они есть (переопределяемые).

састанин
источник
4
Мне просто понадобилось табулирование, и мне посчастливилось найти вашу библиотеку! Работает как шарм: D Если вы слушаете, просто хотел сказать спасибо :)
deepak 06
2
Да, слушаю. Спасибо за добрые слова. Приятно получать положительные отзывы.
sastanin
1
Привет, @sastanin Прежде всего, большое спасибо за такую ​​красивую библиотеку. Могу ли я узнать, есть ли возможность распечатать таблицу на всю ширину терминала?
Validus Oculus
1
Привет, састанин, просто хотел сказать здесь пару слов, чтобы поблагодарить вас за этот очень удобный пакет. Работает как шарм и избавил меня от необходимости писать собственный. Большое спасибо за то, что поделились!
Валентин Б.
1
Ваш список возможностей - преуменьшение. Пробовал ANSI, ускользнул, работает отлично. Спасибо за это!
Red Pill
37

Вот небольшая быстрая и грязная функция, которую я написал для отображения результатов SQL-запросов, которые я могу выполнить только через SOAP API. Ожидается ввод последовательности из одного или нескольких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, можете ли вы показать использование этой функции на примере?
theAlse
1
@MattH, спасибо, но большое число, кажется, сразу его разбивает. TypeError: объект типа int не имеет len ().
theAlse
@Alborz: Я разместил это как отправную точку для других, настройте его для работы с вашими типами данных, если хотите. Хотя в зависимости от того, в какой строке возникла эта ошибка, вы можете не вызывать функцию, как предполагалось
MattH
1
@theAlse Я исправил обнаруженную вами ошибку, len(str(max(...)))добавив ее в строку lens.append. Итак, теперь, если число в столбце шире, чем заголовок столбца, мы все еще в порядке. Кстати, MattH - милое использование аргумента «ключ» для max ()!
nealmcb
19

По какой-то причине, когда я включил «documenttils» в свои поисковые запросы в Google, я наткнулся на текстовую таблицу , которая, похоже, была тем, что я ищу.

kdt
источник
2
Хороший. Отсутствует автоматическое определение ширины столбца; используйте: pastebin.com/SAsPJUxM
Кос,
12

Я тоже написал свое решение по этому поводу. Я старался не усложнять.

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 |
+--------------+--------------+
Робполь86
источник
9

Я только что выпустил таблицы терминов для этой цели. Например, это

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 ,

┌─────────────────┬─────────────────┬─────────────────┐
│ a               │       bb        │             ccc │
╞═════════════════╪═════════════════╪═════════════════╡
│ 123 │
├─────────────────┼─────────────────┼─────────────────┤
│ 613.23236243236613.23236243236613.23236243236 │
└─────────────────┴─────────────────┴─────────────────┘

termtables очень настраиваемый; посмотрите тесты, чтобы увидеть больше примеров.

Нико Шлёмер
источник
Я бы хотел, чтобы вы могли установить максимальное количество отображаемых столбцов и позволить библиотеке обрабатывать логику упаковки.
Кан Мин Ю
7

Вы можете попробовать BeautifulTable . Он делает то, что вы хотите. Вот пример из его документации

>>> 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   |
+----------+------+--------+
Приям Сингх
источник
/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)
Evandrix
/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)
Evandrix
6

Версия с использованием 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│
├─────┼───────┼─────┤
│123    │
├─────┼───────┼─────┤
│456    │
└─────┴───────┴─────┘
Янус Троельсен
источник
5

Если вам нужна таблица с промежутками между столбцами и строками, попробуйте мою сводную таблицу библиотеки

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            |
+----------+------------+---------------------+
dmodo
источник
ERROR: Spans must be a list of lists
cz
2

Я знаю, что вопрос немного устарел, но вот моя попытка:

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

Это немного более читабельно IMHO (хотя он не различает одну / несколько строк, как решения @ MattH, и не использует NamedTuples).

одиночка
источник
2

Я использую эту небольшую служебную функцию.

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       |
-----------------
таван
источник
1
Вы добавляете пробел между каждым столбцом, output += '|' + ''.join([h + ' ' * (l - len(h)) + '|' for h, l in zip(header, max_len)]) + '\n' но не в разделительных линиях. Можно расширить эту строку -с помощью чего-нибудь столь же простого, как output = '-' * (sum(max_len) + 1 + len(header)) + '\n'
ochawkeye 08
1

Вот мое решение:

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)
Люк Тейлор
источник
1
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      | 
------+------------+-
Лепстр
источник
2
Вы можете улучшить свой ответ, состоящий только из кода, дополнив его некоторыми пояснениями.
Юннош 01
0

Это можно сделать с помощью только встроенных модулей, достаточно компактно, используя списки и строковые представления. Принимает список словарей одного формата ...

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
MattK
источник