¿Cómo puedo imprimir de forma bonita tablas ASCII con Python?

Estoy buscando una manera de imprimir bonitas tablas como esta:

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

He encontrado la biblioteca asciitable pero no cumple con los límites, etc. No necesito ningún formato complejo de elementos de datos, solo son cadenas. Lo necesito para auto-dimensionar columnas.

¿Existen otras bibliotecas o métodos, o necesito dedicar unos minutos a escribir los míos?

He leído esta pregunta hace mucho tiempo y terminé de escribir mi propia impresora para tablas: tabulate .

Mi caso de uso es:

  • Quiero un one-liner la mayor parte del tiempo
  • que es lo suficientemente inteligente como para encontrar el mejor formato para mí
  • y puede generar diferentes formatos de texto plano

Dado su ejemplo, la grid es probablemente el formato de salida más similar:

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

Otros formatos admitidos son plain (sin líneas), simple (tablas simples Pandoc), pipe (como tablas en PHP Markdown Extra), orgtbl (como tablas en el modo org de Emacs), orgtbl (como tablas simples en reStructuredText). grid y orgtbl son fácilmente editables en Emacs.

En cuanto al rendimiento, tabulate es ligeramente más lento que asciitable , pero mucho más rápido que PrettyTable y texttable .

PS También soy un gran fan de alinear números por una columna decimal . Así que esta es la alineación por defecto para los números si hay alguna (reemplazable).

Aquí hay una pequeña función rápida y sucia que escribí para mostrar los resultados de las consultas SQL que solo puedo realizar sobre una API SOAP. Espera una entrada de una secuencia de uno o más namedtuples como filas de la tabla. Si solo hay un registro, lo imprime de manera diferente.

Es útil para mí y podría ser un punto de partida para usted:

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

Salida de muestra:

  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

Ejemplo

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

Por alguna razón, cuando incluí ‘docutils’ en mis búsquedas en Google, me topé con texttable , que parece ser lo que estoy buscando.

Yo también escribí mi propia solución a esto. Intenté mantenerlo simple.

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

Versión con w3m diseñada para manejar los tipos que acepta la versión de MattH:

 import subprocess import tempfile import html def pprinttable(rows): esc = lambda x: html.escape(str(x)) sour = "" if len(rows) == 1: for i in range(len(rows[0]._fields)): sour += "" + "".join(["%s" % "".join(["
%s%s" % (esc(rows[0]._fields[i]), esc(rows[0][i])) else: sour += "
%s" % esc(x) for x in rows[0]._fields]) sour += "".join(["
%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])

resultados en:

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

Si quieres una tabla con columnas y filas, prueba mi tabla de tabla

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

Qué salidas:

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

Sé que la pregunta es un poco vieja, pero aquí está mi bash de esto:

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

Es un IMHO un poco más legible (aunque no distingue entre filas simples / múltiples como las soluciones de @ MattH, ni utiliza NamedTuples).

Yo uso esta pequeña función de utilidad.

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

salida

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

Puedes probar BeautifulTable . Hace lo que quieres hacer. Aquí hay un ejemplo de su documentación.

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

Aquí está mi solución:

 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) 

Esto se puede hacer solo con módulos integrados de forma bastante compacta utilizando listas y comprensiones de cadenas. Acepta una lista de diccionarios todos del mismo formato …

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

Ejemplo:

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

Salida:

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

Acabo de lanzar asciiplotlib , y también tiene tablas bonitas. Por ejemplo, este

 import asciiplotlib as apl data = [ [["a", "bb", "ccc"]], [[1, 2, 3], [613.23236243236, 613.23236243236, 613.23236243236]], ] fig = apl.figure() fig.table(data, border_style="thin", ascii_mode=True, padding=(0, 1), alignment="lcr") fig.show() 

te atrapa

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

De forma predeterminada, la tabla se representa con caracteres de dibujo de cuadro Unicode,

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

Las tablas de apl son muy configurables; Echa un vistazo a las pruebas para más ejemplos.