Format tabular data with pretty styles.
Pretty Tables
This is a python package that aims to provide a simple and pretty way of printing tables to the console making use of a class.
The idea started as an attempt to reproduce the behavior of the PM2 package when it displays tables to show data. Later, heavy inspiration came of two other python packages:
- Jazzband's prettytable (The names are similar by accident). Uses a class too.
- Astanin's tabulate. A very simple to use and efficient package.
A big part of the behavior of this package was replicated from these.
Installation
pip install prettyTables on Windows.
pip3 install prettyTables on Linux
Usage
Creating a table is simple.
Code Example
from prettyTables import Table
new_table = Table()
print(new_table)
Output
++
++
++
This is an empty table. It has no data so it only displays this strange thing.
A table that has columns but no rows — a query that came back empty — shows its headers rather than raising:
┌──────┬───────┐
│ name │ value │
╞══════╪═══════╡
└──────┴───────┘
It's possible to add data as columns or rows, or even alternating each one. Any untitled column will be named automatically.
Code Example
new_table.add_column('Name', ['Jade', 'John', 'Jane'])
new_table.add_column('Age', [20, 30, 40])
new_table.add_column('Results', [9.651, 3, 245.7])
print(new_table)
Output
+----------------------+
| Name Age Results |
+======+=====+=========+
| Jade | 20 | 9.651 |
| John | 30 | 3 |
| Jane | 40 | 245.7 |
+------+-----+---------+
Code Example
new_table = Table()
new_table.add_column('Name', ['Jade', 'John', 'Jane'])
new_table.add_column('Age', [20, 30, 40])
new_table.add_column('Test\nResults', [9.651, 3, 245.7])
new_table.add_row(['Piotr\nBaltimore', 27, 3.5])
new_table.add_row(['Sam', 21, 0.6519])
print(new_table)
Output
+----------------------------+
| Name Age Test |
| Results |
+===========+=====+==========+
| Jade | 20 | 9.651 |
| John | 30 | 3 |
| Jane | 40 | 245.7 |
| Piotr | 27 | 3.5 |
| Baltimore | | |
| Sam | 21 | 0.6519 |
+-----------+-----+----------+
As it is visible, the table will format automatically new lines and data types, for now without trying to parse strings that could be converted to another type.
The Table class offers a variety of options that allow things like showing the index of each row, changing the style of the table, hiding the headers, etc.
See style examples here.
Code Example
new_table = Table()
new_table.add_column('Name', ['Jade', 'John'])
new_table.add_column('Age', [20, 30])
new_table.add_column('Test\nResults', [9.651, 3, 245.7])
new_table.add_row(['Piotr\nBaltimore', 27, 3.5])
new_table.add_row(['Sam', 21])
new_table.show_index = True
new_table.style_name = 'pretty_columns'
new_table.missing_value = '?'
print(new_table)
Output
╒═══╤═══════════╤═════╤═════════╕
│ i │ Name │ Age │ Test │
│ │ │ │ Results │
╞═══╪═══════════╪═════╪═════════╡
│ 0 │ Jade │ 20 │ 9.651 │
│ 1 │ John │ 30 │ 3 │
│ 2 │ ? │ ? │ 245.7 │
│ 3 │ Piotr │ 27 │ 3.5 │
│ │ Baltimore │ │ │
│ 4 │ Sam │ 21 │ ? │
╘═══╧═══════════╧═════╧═════════╛
The missing value aligns as if it was of the same type of the other data in the column.
This shows how to get the row and column count. If the index is shown, this count remains unaffected by that column, although, you can get the internal count.
Code Example
new_table.show_index = False
new_table.show_headers = False
print(new_table)
print('row:', new_table.row_count)
print('columns:', new_table.column_count)
new_table.show_index = True
print(new_table)
print('internal_row_count:', new_table.internal_row_count)
print('internal_column_count:', new_table.internal_column_count)
Output
╒═══════════╤════╤═════════╕
│ Jade │ 20 │ 9.651 │
│ John │ 30 │ 3 │
│ ? │ ? │ 245.7 │
│ Piotr │ 27 │ 3.5 │
│ Baltimore │ │ │
│ Sam │ 21 │ ? │
╘═══════════╧════╧═════════╛
row: 5
columns: 3
╒═══╤═══════════╤════╤═════════╕
│ 0 │ Jade │ 20 │ 9.651 │
│ 1 │ John │ 30 │ 3 │
│ 2 │ ? │ ? │ 245.7 │
│ 3 │ Piotr │ 27 │ 3.5 │
│ │ Baltimore │ │ │
│ 4 │ Sam │ 21 │ ? │
╘═══╧═══════════╧════╧═════════╛
internal_row_count: 5
internal_column_count: 4
Colour
Colour the header, the borders, whole columns, whole rows, or individual cells. Specs accept names, attributes, 256-colour indexes and hex triples.
table.header_color = 'bold cyan'
table.border_color = 'grey'
table.column_colors = {'Service': 'bright_white'}
table.color_rule = lambda value, row, column: (
'red' if column == 'Delta' and value < 0 else None
)
color_rule receives the original value, so numeric comparisons work directly.
Colour is emitted only to a terminal by default, and never when NO_COLOR is
set. FORCE_COLOR or table.use_colors = True overrides that. Because
compose() returns a string you may send anywhere, the default errs toward not
embedding escape sequences in something bound for a file.
Widths are measured in terminal columns, not characters, so coloured cells and CJK or emoji data stay aligned.
Reading and writing
Table.from_csv('sales.csv') # numeric columns parsed and aligned
Table.from_html(markup) # standard library parser, no dependency
Table.from_dicts(records)
Table.from_pandas(dataframe) # pip install prettyTables[pandas]
Table.from_excel('report.xlsx') # pip install prettyTables[excel]
table.to_csv('out.csv')
table.to_markdown()
table.to_html('report.html', paginate=25)
table.to_excel('report.xlsx')
table.open_in_browser() # the same page, straight to a browser tab
to_html writes one self-contained file — the stylesheet and the sorting,
filtering and pagination script are inline — so it works offline and from a
file:// URL. open_in_browser renders that page to a temporary file and
opens it, which is the quickest way to give a wide table the room a terminal
cannot: somewhere to scroll sideways, a column to sort by, a box to filter
with. It returns the path it wrote, and takes path= to write somewhere you
choose.
Text formats deliver everything as strings, and a column of strings is
left-aligned. parse_str_numbers converts numeric-looking text so it aligns as
numbers; the readers turn it on for you:
table.parse_str_numbers = True
Values with leading zeros are left alone, so '007' stays a string.
Merging cells
Render a rectangular block as a single cell, across columns, down rows, or both. Coordinates are inclusive, zero-based, and ignore the index column.
table.merge_cells(0, 0, last_column=2, value='Quarter summary')
table.merge_cells(1, 0, last_row=3) # keeps the top-left content
table.merge_cells(0, 0, 2, 2, value='Total', align='r')
A merge never widens the table -- columns are still sized by their unmerged content -- so text longer than its span is truncated.
Merges survive export. HTML and Excel say it in their own words:
table.to_excel('report.xlsx') # real merged cells, ranges and all
table._repr_html_() # <td rowspan="2" colspan="3">
CSV, Markdown and to_records() have no cell that covers its neighbours, so
they follow the convention a spreadsheet uses when saving to one of them: the
merged text goes in the top-left cell of the block and the cells it covered
come out empty. The grid keeps its shape, which is what anything parsing the
file back needs.
In the to_html page the spans are drawn while the table is in the state it
was exported in. Sorting, filtering or paging moves rows away from the
neighbours they were merged with, so those views show the cells unmerged;
clearing the filter brings the spans back.
Performance
An optional C extension accelerates text measurement, and the render pipeline avoids the second measuring pass when the table already fits. On identical data:
| Library | 100x4 | 2000x4 | 10000x6 |
|---|---|---|---|
| prettyTables | 1.0 ms | 16.9 ms | 147 ms |
| prettytable | 1.4 ms | 23.2 ms | 174 ms |
| tabulate | 1.7 ms | 27.5 ms | 193 ms |
pandas .to_string() |
1.7 ms | 22.9 ms | 228 ms |
Reproduce with python3 tools/benchmark.py. This measures one job: turning
data already in memory into formatted text. pandas is an analysis engine and
to_string is a convenience within it, so this says nothing about groupby or
joins.
The extension is built automatically where a compiler is available and falls back to pure Python where it is not, so installation never fails for want of a toolchain.
from prettyTables.fast import implementation
print(implementation()) # 'C extension' or 'pure Python'
Known Issues
None of the three historical known issues remain:
- A column named
"i"no longer collides with the index column (utils.IndexColumnTitle). - Exponential numbers align on the decimal / exponent axis with the rest of the float column.
- Shrinking a float column drops decimals instead of chopping characters, so decimal alignment survives a terminal fit (#23).
Open work and the full issue map live in docs/ISSUES.md.
Project Layout
prettyTables/
├── prettyTables/ the package itself (no runtime dependencies)
│ ├── __init__.py public API: Table, TableComposition, SeparatorLine
│ ├── table.py the Table class — state and render orchestration
│ ├── columns.py type inference, alignment, column widths
│ ├── style_compositions.py the 42 border styles, as data
│ ├── table_strings.py separator lines and data rows
│ ├── cells.py cell padding, justification, wrapping
│ ├── options.py constants and defaults
│ └── utils.py type predicates and small helpers
├── tests/ pytest suite — see ARCHITECTURE.md#testing
├── docs/ issue history and design notes
├── logos/ brand assets — see logos/README.md
├── ARCHITECTURE.md how it all fits together
├── style_examples.md all 42 styles rendered
├── package.json canonical version number; drives the release
└── setup.py reads the version from package.json
Run the tests from the repository root:
pip install -r requirements-dev.txt
python -m pytest
See ARCHITECTURE.md for the rendering pipeline, how styles are defined, and how to add one.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file prettytables-1.5.0.tar.gz.
File metadata
- Download URL: prettytables-1.5.0.tar.gz
- Upload date:
- Size: 143.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b80b3479ad9628b1b9540e72ad07fdec2d8084bb76a2a4282c05ad7b2978bc9d
|
|
| MD5 |
d0a612068630feb16cdedefe685f645c
|
|
| BLAKE2b-256 |
221ff8fc6deeebec4dac76e70335f8a99884782cdec9ccb98fc676c42f88c22b
|