Николай Павлюк
Николай Павлюк
0
Обложка: Как напечатать таблицу с помощью f-string

Как напечатать таблицу с помощью f-string

В этой статье мы разберём как напечатать красивые таблицы:

  • с одинаковой шириной колонок;
  • с разной шириной колонок;
  • с шапкой из двух строк.

А также создадим функции:

  • с параметром максимальной ширины таблицы;
  • для записи таблицы в текстовый файл.

«F-строки» были введены ещё в версии Python 3.6, и все уже давно, наверно, их используют в своём коде. Для тех, кто хочет освежить память, и ещё раз перечитать документацию — PEP 498 — Literal String Interpolation. Мы же будем использовать «f-строки» для вывода данных в табличном виде. Для примера возьмём данные об автомобилях с одного из онлайн-рынков такой структуры:

data = [
    ['Volkswagen', 'Golf V', '2008', '8000', '154000'],
    ['Mazda', 'CX-5', '2013', '14800', '117000'],
    ['Honda', 'CR-V AWD', '2017', '22000', '57000'],
    ['BMW', '320', '2015', '14700', '124000'],
    ['BMW', 'X1', '2012', '17000', '62000'],
    ['Mercedes-Benz', 'E 220', '2009', '9300', '240000'],
    ['Volkswagen', 'Golf VI STYLE', '2011', '9700', '203000'],
    ['Mazda', '6', '2006', '5600', '218000'],
    ['Hyundai', 'Tucson LOUNGE 2009', '2008', '8899', '149000'],
    ['BMW', '520', '2013', '21700', '146000'],
    ['Toyota', 'Highlander', '2015', '28000', '120000'],
    ['Mercedes-Benz', 'E 220', '2005', '8200', '276000'],
    ['BMW', '328', '2012', '12500', '260000'],
    ['Opel', 'Astra J', '2013', '9500', '224000'],
    ['Volkswagen', 'Passat B7', '2013', '11750', '138000'],
    ['Audi', 'A6 Quattro', '2006', '8000', '28000']
    ]

Названия колонок:

columns = ['Марка', 'Модель', 'Год', 'Цена', 'Пробег']

Таблица с одинаковой шириной колонок

Для того, чтоб данные в колонках таблицы выровнять по центру, левому или правому краю, нужно рассчитать ширину колонок и определить отступ. Давайте сделаем ширину колонок одинаковой по максимальной строке столбца и отступом в один символ.

# расчёт максимальной длинны колонок
max_columns = [] # список максимальной длинны колонок
for col in zip(*data):
    len_el = []
    [len_el.append(len(el)) for el in col]  
    max_columns.append(max(len_el))

Печать таблицы:

# печать таблицы с колонками максимальной длинны строки
# печать шапки таблицы
for column in columns:
    print(f'{column:{max(max_columns)+1}}', end='')
print()
# печать разделителя шапки
print(f'{"="*max(max_columns)*5}')
# печать тела таблицы
for el in data:
    for col in el:
        print(f'{col:{max(max_columns)+1}}', end='')
    print()

Здесь в 4 строке кода, после : указана ширина колонок. Так как это не число, а выражение, его обрамляют фигурными скобками. Ширину колонок мы сделали по максимальной длине строки колонок и добавили еще один символ пустой строки '' для отступов, end='' – указывает, что печать будет выводиться без переноса на новую строку. Выравнивание текста в строке по умалчиванию делается по левому краю, поэтому его не указываем. Далее будут рассмотрены все виды выравнивания.

Результат:

Недостатки такой таблицы:

  • большая ширина (подходит для таблиц с примерно одинаковой длинной строк колонок);
  • трудно вычислить длину разделителя шапки.

Таблица с разной шириной колонок

Рассмотрим другой пример. Выведем таблицу с колонками максимальной длины строки каждого столбца.

# вывод таблицы с колонками максимальной длинны строки каждого столбца

# печать шапки таблицы
for n, column in enumerate(columns):
    print(f'{column:{max_columns[n]+1}}', end='')
print()

# печать разделителя шапки '='
r = f'{"="*sum(max_columns)+"="*5}'
print(r[:-1])

# печать тела таблицы
for el in data:
    for n, col in enumerate(el):
        print(f'{col:{max_columns[n]+1}}', end='') # выравнвание по правому краю >
    print()

Результат:

Теперь таблица стала компактнее.

В этом примере названия колонок меньше или равны ширине самих колонок. А что, если они будут больше? Например, состоять из двух слов.

Таблица с шапкой из двух строк

Если ширина названия колонок больше ширины колонок тела таблицы, тогда запишем их в шапке таблицы в две строчки — вот так:

Но теперь колонки названий могут быть шире рассчитанных ранее колонок тела таблицы. Придётся отдельно просчитать максимальные значения ширины колонок шапки и максимальные значения колонок тела таблицы, сравнить их и по большему значению задать ширину столбиков для всей таблицы.

# пишем название колонок в две строчки
columns = [['Марка', 'Модель', 'Год', 'Цена $', 'Пробег км'],
           ['автомобиля', '', 'выпуска', '', '']]

# вычислить максимальную длинну колонки шапки таблицы
max_columns_title = [] # список максимальной ширины колонок шапки
for col in zip(*columns):
    max_columns_title.append(max([len(el) for el in col]))
max_col_title = max(max_columns_title) # максимальная ширина колонки шапки

for col in columns:
    #width = []
    for n, c in enumerate(col):
        
        # сравниваем максимальную колонку шапки с макс колонкой таблицы
        if max_columns[n] >= max_columns_title[n]:
            w = max_columns[n] + 2
            width.append(w)
        else:
            w = max_columns_title[n] + 2
            width.append(w)
            
        # пишем название колонок в две строчки
        print(f'{c:{w}}', end='')
    print()
    
# печать разделителя шапки '='
print(f"{'='*(sum(width)-2)}") 

# печать тела таблицы
for el in data:
    for n, col in enumerate(el):
        print(f'{col:{width[n]}}', end='') 
    print()
print()

 

Результат:

 

Функция с параметром максимальной ширины таблицы

Давайте соберём наш код в функцию, но добавим ещё один параметр — максимальную ширину таблицы. И если ширина таблицы будет больше максимальной, заданной по умалчиванию — выведем сообщение. А также сделаем выравнивание текста в строках шапки таблицы по центру, в теле таблицы — по правому краю. Для этого надо всего лишь перед значением ширины строки вставить символ «^» — выравнивание по центру, «>» — выравнивание по правому краю, «<» – выравнивание по левому краю, выставлено по умолчанию.

def print_table(data, columns, indent, max_width=100):
    # data — список списков, данные таблицы
    # columns — список списков, названия колонок таблицы
    # indent — отступ от края колонки
    # max_widt – допустимая ширина таблицы
    # max_columns — список максимальной длинны строки колонок
    # max_columns_title — список максимальной ширины колонок шапки
    # width — список ширины каждой колонки таблицы для печати
    
    # расчёт макимальной ширины колонок таблицы
    max_columns = []  
    for col in zip(*data):
        len_el = []
        [len_el.append(len(el)) for el in col]  
        max_columns.append(max(len_el))
        
    # вычислить максимальную длинну колонки шапки таблицы
    max_columns_title = [] 
    for col in zip(*columns):
        max_columns_title.append(max([len(el) for el in col]))
    
    # печать таблицы
    for col in columns:
        width = []
        for n, c in enumerate(col):

            # сравниваем максимальную колонку шапки с макс колонкой таблицы
            if max_columns[n] >= max_columns_title[n]:
                w = max_columns[n] + indent
                width.append(w)
            else:
                w = max_columns_title[n] + indent
                width.append(w)

            # пишем название колонок в две строки
            if sum(width) <= max_width:
                print(f'{c:^{w}}', end='') # выравниване по ценру
            else:
                print('Ширина таблицы больше допустимого значения')
                return
        print()

    # печать разделителя шапки '='
    print(f"{'='*(sum(width))}") 
    
    # печать тела таблицы
    for el in data:
        for n, col in enumerate(el):
            print(f'{col:>{width[n]}}', end='') # выравнвание по правому краю 
        print()                      
        
        
print_table(data, columns, 1, max_width=100)

Результат:

Давайте ещё изменим вывод нашей таблицы. Колонки «Цена $» и «Пробег км» выведем с ,, как разделитель тысяч.

def print_table(data, columns, indent, max_width=100):
    # data — список списков, данные таблицы
    # columns — список списков, названия колонок таблицы
    # indent — отступ от края колонки
    # max_widt — допустимая ширина таблицы
    # max_columns — список максимальной длинны строки колонок
    # max_columns_title — список максимальной ширины колонок шапки
    # width — список ширины каждой колонки таблицы для печати
    
    # расчёт макимальной ширины колонок таблицы
    max_columns = []  
    for col in zip(*data):
        len_el = []
        [len_el.append(len(el)) for el in col]  
        max_columns.append(max(len_el))
        
    # вычислить максимальную длинну колонки шапки таблицы
    max_columns_title = [] 
    for col in zip(*columns):
        max_columns_title.append(max([len(el) for el in col]))
    
    # печать таблицы
    for col in columns:
        width = []
        for n, c in enumerate(col):

            # сравниваем максимальную колонку шапки с макс колонкой таблицы
            if max_columns[n] >= max_columns_title[n]:
                w = max_columns[n] + indent
                width.append(w)
            else:
                w = max_columns_title[n] + indent
                width.append(w)

            # пишем название колонок в две строки
            if sum(width) <= max_width:
                print(f'{c:^{w}}', end='') # выравниване по ценру
            else:
                print('Ширина таблицы больше допустимого значения')
                return
        print()

    # печать разделителя шапки '='
    print(f"{'='*(sum(width))}") 
    
    # печать тела таблицы
    for el in data:
        for n, col in enumerate(el):
            if n < 3:
                print(f'{col:>{width[n]}}', end='') # выравнвание по правому краю 
            else:
                print(f'{int(col):{width[n]},}', end='') # выравнвание по правому краю наследуется, с разделителем тысяч «,»
        print()

Здесь, в 52 строке кода, в f'{int(col):{width[n]},}‘ перед закрывающей фигурной скобкой вставлена запятая, как разделитель тысяч (кроме «,» можно ещё использовать «_»), ну а дальше форматирование сделает всё само:

Но это ещё не все возможности «спецификации формата».

Примечание: в F-string в фигурных скобках {} помещены «заменяющие поля». Двоеточие : указывает на поле format_spec, что означает нестандартный формат замены. Для него существует Мини-язык спецификации формата.

Функция для записи таблицы в текстовый файл

Часто приходится не только печатать таблицу, но и сохранять её в текстовом файле. Имея готовую функцию для печати, нетрудно переделать её для записи.

def write_table(file_name, data, columns, indent, max_width=100):
    # file_name — название текстового файла
    # data — список списков, данные таблицы
    # columns — список списков, названия колонок таблицы
    # indent — отступ от края колонки
    # max_widt — допустимая ширина таблицы
    # max_columns — список максимальной длинны строки колонок
    # max_columns_title — список максимальной ширины колонок шапки
    # width — список ширины каждой колонки таблицы для печати
    
    # расчёт макимальной ширины колонок таблицы
    max_columns = []  
    for col in zip(*data):
        len_el = []
        [len_el.append(len(el)) for el in col]  
        max_columns.append(max(len_el))
        
    # вычислить максимальную длинну колонки шапки таблицы
    max_columns_title = [] 
    for col in zip(*columns):
        max_columns_title.append(max([len(el) for el in col]))
    
    # запись таблицы
    with open(file_name, 'w', encoding='utf-8')  as f:
        for col in columns:
            width = []
            for n, c in enumerate(col):

                # сравниваем максимальную колонку шапки с макс колонкой таблицы
                if max_columns[n] >= max_columns_title[n]:
                    w = max_columns[n] + indent
                    width.append(w)
                else:
                    w = max_columns_title[n] + indent
                    width.append(w)

                # пишем название колонок в две строки
                if sum(width) <= max_width:
                    f.write(f'{c:^{w}}')
                else:
                    print('Ширина таблицы больше допустимого значения')
                    return
            f.write('\n')