Як я можу досить друкувати таблиці ASCII за допомогою Python? [зачинено]


81

Я шукаю спосіб досить друкувати такі таблиці:

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

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

Чи існують інші бібліотеки чи методи, чи мені потрібно витратити кілька хвилин на написання власних?


Чому б не використовувати документи, щоб зробити це за вас?
С.Лотт

Як ви називаєте стіл? Як дані організовані в таблиці? Чи значення1, значення2, значення3, значення4 ... є послідовними значеннями у списку? Я думаю, що fomat () достатньо, щоб отримати такий простий дисплей, не потрібно довго вивчати підручник, який пояснює, як заробити час за допомогою бібліотеки
eyquem

2
@korona: Ні, я не робив пропозиції. Я задавав питання. Я не маю уявлення, що @kdt знає чи не знає. Замість того, щоб припускати, я відчуваю, що змушений запитати.
S.Lott,

5
Мені здалося, що ти насправді припускав, що він знає про документи. Може, він цього не робить?
корона

2
@ S.Lott Я розглядав документи, і хоча це, звичайно, чудово підходить для перетворення тексту в html, латекс тощо, я не бачу способу створення приємних текстових таблиць зі стовпцями, які вирівнюються і виглядають досить шрифти фіксованої ширини. Ви неправильно зрозуміли мету kdt, чи мені чогось не вистачає?
nealmcb

Відповіді:


71

Я давно прочитав це запитання і закінчив писати власний симпатичний принтер для таблиць: 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

2
Так, я слухаю. Дякую за добрі слова. Дуже приємно отримати позитивні відгуки.
зустрічі

1
Привіт, @sastanin Перш за все, дякую тобі за таку приємну бібліотеку. Чи можу я знати, чи є варіант друку таблиці на всю ширину терміналу?
Validus Oculus

1
Привіт, зустрічі, я просто хотів залишити тут слово, щоб подякувати вам за цей дуже зручний пакет. Працює як оберіг і врятував мене від проблеми написання власного. Велике спасибі за обмін!
Валентин Б.

1
Ваш список функцій є заниженим. Пробував ансі втекти речі, працює ідеально. Дякую за це!
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 дякую, але велика кількість, здається, негайно розбиває це. Помилка типу: об'єкт типу 'int' не має значення len ().
theAlse

@Alborz: Я розмістив це як вихідну точку для інших, налаштуйте його, щоб мати справу з вашими типами даних, якщо хочете. Хоча залежно від того, з якого рядка виникла ця помилка, можливо, ви не викликаєте функцію за призначенням
MattH

1
@theAlse Я виправив виявлену вами помилку, зробивши її len(str(max(...)))в об'єктиві. додайте рядок. Отже, якщо число в стовпці ширше, ніж заголовок стовпця, ми все ще добре. BTW, MattH - миле використання аргументу "ключ" для max ()!
nealmcb

19

Чомусь, коли я включав "документи" у свої пошуки в Google, я натрапив на текстову таблицю , яка, здається, є тим, що я шукаю.


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

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 │
└─────────────────┴─────────────────┴─────────────────┘

терміни дуже налаштовуються; перегляньте тести, щоб побачити більше прикладів.


Я хотів би, щоб ви могли встановити максимальний стовпець для відображення і дозволити бібліотеці обробляти логіку обтікання.
Кан Мін Ю

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

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' але не в рядках-роздільниках. Може продовжити цей ряд -s чимось таким простим, як output = '-' * (sum(max_len) + 1 + len(header)) + '\n'
ochawkeye

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
Ви можете покращити свою відповідь лише за кодом, доповнивши її деяким поясненням.
Yunnosch

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.