Сообщить об ошибке.

Использование string.Template для создания отчетов

В повседневной работе часто приходится генерировать определенные отчеты с сохранением в файлы. Общим для всех этих ситуаций является то, что создание отчета или манипулирование строками следуют определенному шаблону. Обычно эти шаблоны настолько похожи, что их желательно использовать повторно. Для таких случаев, Python предоставляет класс string.Template().

Этот материал показывает, как использовать класс string.Template() для создания выходных файлов на основе данных. Будет разобран пример, часто используемый в повседневной работе. Это реальный инструмент, который использует этот класс для построения файловых отчетов.

Примечание: В примерах используется Python 3.9.

Преимущества использования string.Template по сравнению с другими решениями:

  • Никаких дополнительных зависимостей не требуется: он работает "из коробки" со стандартной установкой Python, поэтому установка сторонних модулей не требуется.
  • Он легкий: конечно, шаблонизаторы, такие как Jinja2 и Mako, широко используются и даже предоставляют определенную логику в шаблонах. В описанных сценариях эти возможности просто избыточны.
  • Разделение логики и представления данных: класс string.Template позволяет убрать встроенные в код операции со строками и использовать текстовый файл шаблона для генерации отчета в файл. Что позволяет НЕ изменять код Python, а только редактировать текстовый файл шаблона, если поменялась структура или дизайн отчета.

Создание отчетов с классом string.Template.

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

На данный момент не волнует, откуда берутся данные или какие книги входят в этот список. Для простоты предположим, что есть программа Python которая генерирует JSON-файл с именем data.json, представляющий собой сопоставление имени автора и названия книги, как показано ниже.

{
    "Dale Carnegie": "How To Win Friends And Influence People",
    "Daniel Kahneman": "Thinking, Fast and Slow",
    "Leo Tolstoy": "Anna Karenina",
    "William Shakespeare": "Hamlet",
    "Franz Kafka": "The Trial"
}

Теперь задача состоит в том, чтобы визуализировать этот список книг таким образом, чтобы им могли поделиться с другими, например, какие-то журналы или блогеры. Руководство решило, что простой таблицы в формате HTML будет достаточно. Встает вопрос: Как сгенерировать эту HTML-таблицу?

Конечно, можно сделать это вручную например, перенести текст в Excel, который генерирует программа или изменить код программы Python. Но в будущем желательно иметь более общую версию, что бы снова не пришлось выполнять дополнительную работу, т.к. список может быть расширен, а структура или дизайн могут измениться.

Возникшая ситуация идеальна для использования класса Python string.Template! Начнем с создания фактического шаблона, который показан ниже. Назовем его template.html.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Great Books of All Time</title>
</head>
<body>
    <div class="container">
        <h1>Great Books of All Time</h1>
        <table class="table">
            <thead>
                <tr>
                    <th scope="col">#</th>
                    <th scope="col">Author</th>
                    <th scope="col">Book Title</th>
                </tr>
            </thead>
            <tbody>
                ${elements}
            </tbody>
        </table>
    </div>
</body>
</html>

Сам файл очень примитивный. Для стилизации можно использовать CSS фреймворк bootstrap или создать собственные CSS-стили. В этом шаблоне создали базовую структуру финальной таблицы с заголовки столбцов, но данные все еще отсутствуют. Обратите внимание, что в элементе <tbody> используется заполнитель ${elements}, это точка, куда позже будет вставлен список книг.

Теперь реализуем Python-скрипт, генерирующий желаемый результат. Создадим новый Python-файл с именем report.py в текущем рабочем каталоге. Сначала импортируем два необходимых нам встроенных модуля и загружаем данные из JSON-файла.

# report.py
import json
import string

with open("data.json") as f:
    data = json.loads(f.read())

Переменная data представляет собой словарь, содержащий имя автора (key) и название книги (value) в виде пар key:value. Далее генерируем HTML-таблицу, которую поместим в переменную шаблона ${elements}. Инициализируем пустую строку, к которой будем добавлять новые строки таблицы, как показано ниже.

content = ""
for i, (author, title) in enumerate(data.items(), 1):
    content += "<tr>"
    content += f"<td>{i}</td>"
    content += f"<td>{author}</td>"
    content += f"<td>{title}</td>"
    content += "</tr>"

Фрагмент кода перебирает все элементы в словаре data и помещает название книги и имя автора в соответствующие HTML-теги. Другими словами создаются сердцевина HTML-таблицы, которая вставляется в <tbody>. На следующем шаге загрузим файл шаблона, созданный ранее:

with open("template.html") as tpl:
    template = string.Template(tpl.read())

Обратите внимание, что string.Template принимает строку, а не путь к файлу. Следовательно, можно указать строки, созданные ранее в программе, без необходимости их сохранения в файл. В текущей ситуации, классу передается содержимое файла template.html.

Чтобы заменить элемент-заполнитель ${elements} строкой, хранящейся в переменной content, необходимо использовать метод шаблона Template.replace(). Метод возвращает строку, которую сохраним в переменной final_output. И последнее, но не менее важное: создадим новый файл с именем report.html и запишем в него окончательный вывод.

final_output = template.substitute(elements=content)
with open("report.html", "w") as output:
    output.write(final_output)

Безопасная замена заполнителя шаблона.

Что значит безопасная замена заполнителя в шаблоне? Например есть строка, в которую необходимо вставить имя и фамилию человека. Это можно сделать следующим образом:

# safe_substitution.py
import string

template_string = "Тебя зовут ${firstname} ${lastname}"
tpl = string.Template(template_string)
result = tpl.substitute(firstname="Женя", lastname="Данилов")
print(result)

Что может произойти, например, в "базе данных" может быть пропущено значение для фамилии и при заполнении вылезет ошибка KeyError. Чтобы предотвратить этот сценарий, можно использовать метод Template.safe_substitution(). В этом контексте, безопасный означает, что Python в любом случае пытается вернуть допустимую строку. Следовательно, если значение не найдено, заполнитель просто не заменяется.

Скорректируем код следующим образом:

# safe_substitution.py
import string

template_string = "Тебя зовут ${firstname} ${lastname}"
tpl = string.Template(template_string)
result = tpl.safe_substitute(firstname="Женя")
print(result)  # Тебя зовут Женя.

Конечно могут быть варианты более элегантного решения или даже желаемого поведения, но имейте в виду, что это может вызвать непреднамеренные побочные эффекты в другом месте кода.