Wie kann ich mit Python ASCII-Tabellen hübsch drucken? [geschlossen]

80

Ich suche nach einer Möglichkeit, Tabellen wie diese hübsch zu drucken:

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

Ich habe die Asciitable- Bibliothek gefunden, aber sie macht keine Grenzen usw. Ich brauche keine komplexe Formatierung von Datenelementen, sie sind nur Zeichenfolgen. Ich brauche es, um Spalten automatisch zu dimensionieren.

Gibt es andere Bibliotheken oder Methoden oder muss ich einige Minuten damit verbringen, meine eigenen zu schreiben?

kdt
quelle
Warum nicht docutils verwenden, um dies für Sie zu tun?
S.Lott
Wie nennt man einen Tisch? Wie sind die Daten in einer Tabelle organisiert? Sind Wert1, Wert2, Wert3, Wert4 ... die aufeinander folgenden Werte in einer Liste? Ich denke, dass fomat () ausreicht, um eine so einfache Anzeige zu erhalten, dass man nicht lange ein Tutorial lernen muss, in dem erklärt wird, wie man mit einer Bibliothek Zeit
gewinnt
2
@korona: Nein, ich habe keinen Vorschlag gemacht. Ich habe eine Frage gestellt. Ich habe keine Ahnung, was @kdt weiß oder nicht weiß. Anstatt anzunehmen, fühle ich mich gezwungen zu fragen.
S.Lott
5
Es klang für mich so, als würden Sie tatsächlich davon ausgehen, dass er sich mit Docutils auskennt. Vielleicht tut er es nicht?
Korona
2
@ S.Lott Ich habe mir docutils angesehen, und obwohl es natürlich großartig ist, Text in HTML, Latex usw. zu konvertieren, sehe ich keine Möglichkeit, schöne Texttabellen mit Spalten zu generieren , die in einer Reihe stehen und hübsch aussehen Schriftarten mit fester Breite. Haben Sie das Ziel von kdt falsch verstanden oder fehlt mir etwas?
Nealmcb

Antworten:

67

Ich habe diese Frage vor langer Zeit gelesen und meinen eigenen hübschen Drucker für Tabellen fertig geschrieben : tabulate.

Mein Anwendungsfall ist:

  • Ich möchte die meiste Zeit einen Einzeiler
  • Das ist klug genug, um die beste Formatierung für mich zu finden
  • und kann verschiedene Klartextformate ausgeben

In Ihrem Beispiel gridist wahrscheinlich das ähnlichste Ausgabeformat:

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

Andere unterstützte Formate sind plain(keine Zeilen), simple(einfache Pandoc-Tabellen) pipe(wie Tabellen in PHP Markdown Extra) orgtbl(wie Tabellen im Emacs-Organisationsmodus) rst(wie einfache Tabellen in reStructuredText). gridund orgtblsind in Emacs leicht zu bearbeiten.

In Bezug auf die Leistung tabulateist etwas langsamer als asciitable, aber viel schneller als PrettyTableund texttable.

PS Ich bin auch ein großer Fan davon, Zahlen durch eine Dezimalspalte auszurichten . Dies ist also die Standardausrichtung für Zahlen, falls vorhanden (überschreibbar).

Sastanin
quelle
2
Ich brauchte gerade eine tabellarische Lösung und hatte das Glück, Ihre Bibliothek zu finden! Funktioniert wie ein Zauber: D Falls Sie zuhören, wollte ich nur Danke sagen :)
deepak
1
Ja ich höre. Wir danken Ihnen für die freundlichen Worte. Es ist wirklich schön, positives Feedback zu bekommen.
Sastanin
Hallo, @sastanin Zunächst einmal vielen Dank für diese schöne Bibliothek. Darf ich wissen, ob es eine Option gibt, eine Tabelle zu drucken, die sich über die gesamte Breite des Terminals erstreckt?
Validus Oculus
Hallo Sastanin, ich wollte hier nur ein Wort sagen, um Ihnen für dieses sehr praktische Paket zu danken. Funktioniert wie ein Zauber und erspart mir die Mühe, meine eigenen zu schreiben. Vielen Dank für das Teilen!
Valentin B.
Ihre Feature-Liste ist eine Untertreibung. Versi Ansi entkommen Sachen, funktioniert perfekt. Danke dafür!
Rote Pille
37

Hier ist eine schnelle und schmutzige kleine Funktion, die ich geschrieben habe, um die Ergebnisse von SQL-Abfragen anzuzeigen, die ich nur über eine SOAP-API durchführen kann. Es erwartet eine Eingabe einer Folge von einer oder mehreren namedtuplesals Tabellenzeilen. Wenn es nur einen Datensatz gibt, wird dieser anders ausgedruckt.

Es ist praktisch für mich und könnte ein Ausgangspunkt für Sie sein:

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

Beispielausgabe:

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

Beispiel

>>> 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
quelle
@MattH können Sie die Verwendung dieser Funktion anhand eines Beispiels zeigen?
theAlse
1
@ MattH danke, aber eine große Anzahl scheint es sofort zum Absturz zu bringen. TypeError: Objekt vom Typ 'int' hat kein len ().
theAlse
@Alborz: Ich habe dies als Ausgangspunkt für andere veröffentlicht. Passen Sie es an Ihre Datentypen an, wenn Sie möchten. Abhängig davon, aus welcher Zeile dieser Fehler stammt, rufen Sie die Funktion möglicherweise nicht wie beabsichtigt auf
MattH
1
@theAlse Ich habe den von Ihnen identifizierten Fehler behoben, indem ich ihn len(str(max(...)))in der Zeile object.append erstellt habe. Wenn also eine Zahl in einer Spalte breiter als die Spaltenüberschrift ist, sind wir immer noch gut. Übrigens, MattH - niedliche Verwendung des "Schlüssel" -Arguments für max ()!
Nealmcb
18

Aus irgendeinem Grund bin ich bei der Aufnahme von "docutils" in meine Google-Suche auf eine Texttabelle gestoßen , nach der ich anscheinend gesucht habe.

kdt
quelle
2
Schön. Fehlt die automatische Erkennung der Spaltenbreite; Verwenden Sie: pastebin.com/SAsPJUxM
Kos
12

Auch ich habe meine eigene Lösung dafür geschrieben. Ich habe versucht, es einfach zu halten.

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

Ich habe gerade Termtabellen für diesen Zweck veröffentlicht. Zum Beispiel dies

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

holt dich

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

Standardmäßig wird die Tabelle mit Unicode gerendert Box-Zeichnen von Buchstaben ,

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

Termtabellen sind sehr konfigurierbar; Weitere Beispiele finden Sie in den Tests .

Nico Schlömer
quelle
Ich wünschte, Sie könnten die maximale Spalte festlegen, die angezeigt werden soll, und die Bibliothek die Umbruchlogik übernehmen lassen.
Kang Min Yoo
7

Sie können BeautifulTable ausprobieren . Es macht was du willst. Hier ist ein Beispiel aus der Dokumentation

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

Version mit w3m, die für die Typen entwickelt wurde Die Version von MattH akzeptiert:

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

Ergebnisse in:

┌───────┬─┐
│ first │1│
├───────┼─┤
│second │2│
├───────┼─┤
│ third │3│
└───────┴─┘
┌─────┬───────┬─────┐
│first│second │third│
├─────┼───────┼─────┤
│123    │
├─────┼───────┼─────┤
│456    │
└─────┴───────┴─────┘
Janus Troelsen
quelle
5

Wenn Sie eine Tabelle mit Spalten- und Zeilenbereichen möchten, versuchen Sie es mit meiner Bibliothek 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))

Welche Ausgänge:

+----------+------------+----------+----------+
| 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
quelle
ERROR: Spans must be a list of lists
cz
2

Ich weiß, dass die Frage etwas alt ist, aber hier ist mein Versuch:

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

Es ist meiner Meinung nach etwas besser lesbar (obwohl es nicht zwischen einzelnen / mehreren Zeilen unterscheidet, wie es die Lösungen von @ MattH tun, noch NamedTuples verwendet).

lonetwin
quelle
2

Ich benutze diese kleine Utility-Funktion.

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

Ausgabe

-----------------
|header 1|header 2|
-----------------
|1       |2       |
|3       |4       |
-----------------
thavan
quelle
Sie fügen zwischen jeder Spalte ein Leerzeichen ein, output += '|' + ''.join([h + ' ' * (l - len(h)) + '|' for h, l in zip(header, max_len)]) + '\n' jedoch nicht in den Trennlinien. Kann diese Reihe von -s mit etwas so Einfachem wie output = '-' * (sum(max_len) + 1 + len(header)) + '\n'
ochawkeye
1

Hier ist meine Lösung:

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

Beispiel:

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

create_table(table)

Ausgabe:

uid   | name       | 
------+------------+-
0     | Jon        | 
1     | Doe        | 
2     | Lemma      | 
3     | Hemma      | 
------+------------+-
Lepstr
quelle
2
Sie können Ihre Nur-Code-Antwort verbessern, indem Sie sie mit einigen Erklärungen ergänzen.
Yunnosch
0

Dies kann mit nur eingebauten Modulen ziemlich kompakt unter Verwendung von Listen- und Zeichenfolgenverständnissen durchgeführt werden. Akzeptiert eine Liste von Wörterbüchern im gleichen Format ...

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
quelle