Вывести в консоль?
Ну я бы подходил к этому так.
1. Преобразовать все выводимые данные в строки и разбить их по переносам строки.
2. Определить наибольшую ширину каждого столбца с учётом переносов строки. Для каждой ячейки вычисляем наибольшую ширину строки, потом ищем максимум по столбцу. Можно добавить 1-2 символа как "поля", если надо.
3. При выводе строки, выводим строки ячеек вместе, используя
itertools.zip_longest(). Т.е. сначала первую строку в каждой ячейке, потом вторую, и т.д. Если получили вместо одной из строк None, значит в этой ячейке строки уже закончились - выводим пробелы.
Ширину вывода каждой ячейки мы знаем из пункта 2, выровнять значение пробелами - тривиально.
# -*- coding: utf-8 -*-
import typing as t
import itertools
def print_table(headers: t.Dict[str, str], data: t.Iterable[t.Dict[str, str]]) -> None:
keys: t.List[str] = list(headers.keys()) #список ключей в таблице - чтобы сохранять порядок столбцов
split_data: t.List[t.Dict[str, t.List[str]]] = [] #ячейки, разбитые на строки
max_widths: t.Dict[str, int] = { key:len(value) for key,value in headers.items() } #ширина каждого столбца в таблице
for line in data:
#разбиваем ячейки строки на текстовые строки по \n
split_line: t.Dict[str, t.List[str]] = { key:value.splitlines() for key,value in line.items() }
#обновляем ширину столбцов, если надо
for key in keys:
new_width = max(map(len, split_line.get(key, [''])))
if new_width > max_widths[key]:
max_widths[key] = new_width
split_data.append(split_line)
#выводим заголовки
for key in keys:
print(f'{{0:<{max_widths[key]}}}'.format(headers[key]), end='|') #можно вместо | поставить пробел
print()
print( '+'.join('-'*v for v in max_widths.values()) + '|') #разделитель заголовка и тела таблицы
#выводим строки таблицы
for row in split_data:
for parts in itertools.zip_longest(*(row[key] for key in keys)):
#parts - кортеж, где каждый элемент либо строка в очередной ячейке, либо None
for key,part in zip(keys, parts):
#None означает, что в этой ячейке строки текста уже кончились
print(f'{{0:<{max_widths[key]}}}'.format(part if part is not None else ''), end='|')
print()
print( '+'.join('-'*v for v in max_widths.values()) + '|') #разделитель строк, если надо
data = [
{'ip':'192.168.0.2', 'model':'DES-3200-26', 'uptime': '3d 12:03:05', 'uplink state': '25: up\n26:up', 'uplink err': '0\n11', 'uplink mcast': '24560\n113'},
{'ip':'192.168.0.2', 'model':'DES-3200-52', 'uptime': '1d 04:00:15', 'uplink state': '49: up\n50:up\n51:down\n52:down', 'uplink err': '10\n1133\n0\n0', 'uplink mcast': '5497812\n3145\n0\n0'},
]
headers = {'ip': 'IP address', 'model': 'Model', 'uptime': 'Uptime', 'uplink state': 'Uplink state', 'uplink err': 'Uplink errors', 'uplink mcast': 'Uplink M-cast'}
print_table(headers, data)
IP address |Model |Uptime |Uplink state|Uplink errors|Uplink M-cast|
-----------+-----------+-----------+------------+-------------+-------------|
192.168.0.2|DES-3200-26|3d 12:03:05|25: up |0 |24560 |
| | |26:up |11 |113 |
-----------+-----------+-----------+------------+-------------+-------------|
192.168.0.2|DES-3200-52|1d 04:00:15|49: up |10 |5497812 |
| | |50:up |1133 |3145 |
| | |51:down |0 |0 |
| | |52:down |0 |0 |
-----------+-----------+-----------+------------+-------------+-------------|