Jak mogę ładnie wydrukować tabele ASCII w Pythonie? [Zamknięte]

81

Szukam sposobu na ładne wydrukowanie tabel w ten sposób:

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

Znalazłem bibliotekę asciitable, ale nie robi ona granic itp. Nie potrzebuję żadnego skomplikowanego formatowania elementów danych, to tylko ciągi znaków. Potrzebuję tego do automatycznego rozmiaru kolumn.

Czy istnieją inne biblioteki lub metody, czy też muszę poświęcić kilka minut na pisanie własnych?

kdt
źródło
Dlaczego nie skorzystać z docutils, aby zrobić to za Ciebie?
S.Lott
Jak nazywasz stół? Jak są zorganizowane dane w tabeli? Czy wartość1, wartość2, wartość3, wartość4 ... to kolejne wartości na liście? Myślę, że fomat () wystarczy do uzyskania tak prostego wyświetlacza, nie trzeba długo uczyć się samouczka, który wyjaśnia, jak zyskać na czasie korzystanie z biblioteki
eyquem
2
@korona: Nie, nie sugerowałem. Zadawałem pytanie. Nie mam pojęcia, co @kdt wie, a czego nie. Zamiast zakładać, czuję się zmuszony zapytać.
S.Lott
5
Brzmiało to tak, jakbyś faktycznie zakładał, że on wie o docutils. Może nie?
korona
2
@ S.Lott Spojrzałem na docutils i chociaż jest to oczywiście świetne do konwersji tekstu do html, latex itp., Nie widzę sposobu na generowanie ładnych tabel tekstowych z kolumnami, które są wyrównane i ładnie wyglądają czcionki o stałej szerokości. Czy źle zrozumiałeś cel kdt, czy coś mi brakuje?
nealmcb

Odpowiedzi:

72

Czytałem to pytanie dawno temu i skończył pisać własne ładnego drukarkę do tabel: tabulate.

Mój przypadek użycia to:

  • Przez większość czasu chcę mieć jedną linijkę
  • co jest na tyle sprytne, by znaleźć dla mnie najlepsze formatowanie
  • i może wyświetlać różne formaty zwykłego tekstu

Biorąc pod uwagę twój przykład, gridjest to prawdopodobnie najbardziej podobny format wyjściowy:

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

Inne obsługiwane formaty to plain(bez linii), simple(proste tabele Pandoc), pipe(jak tabele w PHP Markdown Extra), orgtbl(takie jak tabele w trybie organizacyjnym Emacsa), rst(jak proste tabele w reStructuredText). gridi orgtblmożna je łatwo edytować w Emacsie.

Pod względem wydajności tabulatejest nieco wolniejszy niż asciitable, ale znacznie szybszy niż PrettyTablei texttable.

PS Jestem też wielkim fanem wyrównywania liczb w kolumnie dziesiętnej . Jest to więc domyślne wyrównanie liczb, jeśli takie istnieją (można je zastąpić).

śastanin
źródło
4
Po prostu potrzebowałem rozwiązania tabelarycznego i miałem szczęście znaleźć Twoją bibliotekę! Działa jak marzenie: D Jeśli słuchasz, chciałem tylko podziękować :)
deepak
2
Tak, słucham. Dziękuję za miłe słowa. Bardzo miło jest otrzymywać pozytywne opinie.
sastanin
1
Cześć @sastanin Przede wszystkim bardzo dziękuję za tak ładną bibliotekę. Czy mogę wiedzieć, czy istnieje opcja drukowania tabeli na całą szerokość terminala?
Validus Oculus
1
Witaj sastaninie, chciałem tylko napisać tutaj słowo, aby podziękować za ten bardzo przydatny pakiet. Działa jak urok i oszczędza mi kłopotów z pisaniem własnych. Wielkie dzięki za udostępnienie!
Valentin B.,
1
Twoja lista funkcji to mało powiedziane. Próbowałem rzeczy, które uciekły, działa idealnie. Dzięki za to!
Red Pill
37

Oto krótka, szybka i brudna funkcja, którą napisałem do wyświetlania wyników zapytań SQL, które mogę wykonać tylko za pośrednictwem interfejsu API SOAP. Oczekuje wprowadzenia sekwencji jednego lub więcej namedtuplesjako wierszy tabeli. Jeśli jest tylko jeden rekord, drukuje go inaczej.

Jest to dla mnie przydatne i może być dla Ciebie punktem wyjścia:

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

Przykładowe dane wyjściowe:

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

Przykład

>>> 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
źródło
@MattH czy możesz pokazać użycie tej funkcji na przykładzie?
theAlse
1
@MattH dzięki, ale duża liczba wydaje się natychmiast go zawieszać. TypeError: obiekt typu „int” nie ma len ().
theAlse
@Alborz: Opublikowałem to jako punkt wyjścia dla innych, dostosuj go do swoich typów danych, jeśli chcesz. Chociaż w zależności od tego, z której linii pochodzi ten błąd, możesz nie wywoływać funkcji zgodnie z przeznaczeniem
MattH,
1
@theAlse Naprawiłem zidentyfikowany przez Ciebie błąd, umieszczając go len(str(max(...)))w linii lens.append. Więc teraz, jeśli liczba w kolumnie jest szersza niż nagłówek kolumny, nadal jesteśmy w porządku. BTW, MattH - urocze użycie argumentu „klucz” w funkcji max ()!
nealmcb
19

Z jakiegoś powodu, kiedy dodałem „docutils” do moich wyszukiwań w Google, natknąłem się na texttable , który wydaje się być tym, czego szukam.

kdt
źródło
2
Niezłe. Brak automatycznego wykrywania szerokości kolumny; użyj: pastebin.com/SAsPJUxM
Kos
12

Ja też napisałem własne rozwiązanie tego problemu. Starałem się, żeby to było proste.

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 |
+--------------+--------------+
Robpol86
źródło
9

Właśnie w tym celu opublikowałem tabele terminów . Na przykład this

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

dostaje cię

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

Domyślnie tabela jest renderowana za pomocą znaków rysunkowych Unicode ,

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

tabele terminali są bardzo konfigurowalne; sprawdź testy, aby uzyskać więcej przykładów.

Nico Schlömer
źródło
Chciałbym, żebyś mógł ustawić maksymalną wyświetlaną kolumnę i pozwolić bibliotece na obsługę logiki zawijania.
Kang Min Yoo
7

Możesz spróbować BeautifulTable . Robi to, co chcesz. Oto przykład z jego dokumentacji

>>> 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   |
+----------+------+--------+
Priyam Singh
źródło
/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

Wersja wykorzystująca w3m zaprojektowana do obsługi typów, które akceptuje wersja 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])

prowadzi do:

┌───────┬─┐
│ first │1│
├───────┼─┤
│second │2│
├───────┼─┤
│ third │3│
└───────┴─┘
┌─────┬───────┬─────┐
│first│second │third│
├─────┼───────┼─────┤
│123    │
├─────┼───────┼─────┤
│456    │
└─────┴───────┴─────┘
Janus Troelsen
źródło
5

Jeśli potrzebujesz tabeli z rozpiętościami kolumn i wierszy, wypróbuj moją tabelę rozdzielczą biblioteki

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

Które wyjścia:

+----------+------------+----------+----------+
| 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
źródło
ERROR: Spans must be a list of lists
cz
2

Wiem, że to pytanie jest trochę stare, ale oto moja próba:

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

Jest nieco bardziej czytelny IMHO (chociaż nie rozróżnia pojedynczych / wielu wierszy, jak rozwiązania @ MattH, ani nie używa NamedTuples).

lonetwin
źródło
2

Używam tej małej funkcji użytkowej.

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

wynik

-----------------
|header 1|header 2|
-----------------
|1       |2       |
|3       |4       |
-----------------
thavan
źródło
1
Dodajesz spację między każdą kolumną, output += '|' + ''.join([h + ' ' * (l - len(h)) + '|' for h, l in zip(header, max_len)]) + '\n' ale nie w liniach separatora. Można rozszerzyć ten wiersz o -coś tak prostego, jak output = '-' * (sum(max_len) + 1 + len(header)) + '\n'
ochawkeye
1

Oto moje rozwiązanie:

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)
Luke Taylor
źródło
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()

Przykład:

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

create_table(table)

Wynik:

uid   | name       | 
------+------------+-
0     | Jon        | 
1     | Doe        | 
2     | Lemma      | 
3     | Hemma      | 
------+------------+-
Lepstr
źródło
2
Możesz poprawić odpowiedź zawierającą tylko kod, rozszerzając ją o kilka wyjaśnień.
Yunnosch
-1

Można to zrobić tylko za pomocą wbudowanych modułów, dość zwięźle, używając list i łańcuchów znaków. Akceptuje listę słowników w tym samym formacie ...

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
źródło
Prześlij przykład, jak używać swojego kodu. tableit([dict(a='1', b='2', c='3'), dict(a='x', b='y', c='z')])
iuridiniz
Twój kod nie działa bez zmian w Pythonie 3, wiersz 2 musi być:lengths = [ max(list(map(lambda x:len(x.get(k)), dictlist)) + [len(k)]) for k in dictlist[0].keys() ]
iuridiniz