Рубрики
Без рубрики

Форматирование строк с помощью класса шаблонов Python

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

Автор оригинала: Leodanis Pozo Ramos.

Вступление

Шаблоны Python используются для замены данных в строки. С помощью шаблонов мы получаем сильно настраиваемый интерфейс для подстановки строк (или интерполяции строк).

Python уже предлагает множество способов замены строк , включая недавно введенные f-строки . Хотя замена строк шаблонами встречается реже, ее сила заключается в том, как мы можем настроить наши правила форматирования строк.

В этой статье мы отформатируем строки с помощью класса Python Template . Затем мы рассмотрим, как мы можем изменить способ, которым наши шаблоны могут заменять данные в строки.

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

Понимание класса шаблонов Python

Класс Python Template был добавлен в модуль string начиная с Python 2.4. Этот класс предназначен для использования в качестве альтернативы встроенным опциям подстановки (в основном в % ) для создания сложных шаблонов на основе строк и для обработки их удобным для пользователя способом.

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

  • Символ $
  • Допустимый идентификатор Python. Идентификатор-это любая последовательность прописных и строчных букв от А до Я, символов подчеркивания ( _ ) и цифр от 0 до 9. Идентификатор не может начинаться с цифр и не может быть ключевым словом Python.

В строке шаблона $name и $age будут считаться допустимыми заполнителями.

Чтобы использовать класс Python Template в нашем коде, нам нужно:

  1. Импорт Шаблона из модуля string
  2. Создайте допустимую строку шаблона
  3. Instantiate Template использование строки шаблона в качестве аргумента
  4. Выполните подстановку с помощью метода подстановки

Вот основной пример того, как мы можем использовать класс Python Template в нашем коде:

>>> from string import Template
>>> temp_str = 'Hi $name, welcome to $site'
>>> temp_obj = Template(temp_str)
>>> temp_obj.substitute(name='John Doe', site='StackAbuse.com')
'Hi John Doe, welcome to StackAbuse.com'

Мы замечаем , что при построении строки шаблона temp_str мы используем два заполнителя: $name и $site . Знак $ выполняет фактическую подстановку, а идентификаторы ( name и site ) используются для сопоставления заполнителей с конкретными объектами, которые нам нужно вставить в строку шаблона.

Магия завершается, когда мы используем метод substitute () для выполнения подстановки и построения нужной строки. Подумайте о substitute() так, как если бы мы говорили Python , пройдите через эту строку и, если вы найдете $name , замените ее на John Doe . Продолжайте поиск по строке и, если вы найдете идентификатор $site , превратите его в StackAbuse.com .

Имена аргументов, которые мы передаем в .substitute () , должны совпадать с идентификаторами, которые мы использовали в заполнителях нашей строки шаблона.

Самое важное различие между Template и остальными инструментами подстановки строк, доступными в Python, заключается в том, что тип аргумента не учитывается. Мы можем передать любой тип объекта, который может быть преобразован в допустимую строку Python. Класс Template автоматически преобразует эти объекты в строки, а затем вставляет их в конечную строку.

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

Строка Шаблона

Строка шаблона-это обычная строка Python, включающая специальные заполнители. Как мы уже видели, эти заполнители создаются с помощью знака $ вместе с допустимым идентификатором Python. Как только у нас есть допустимая строка шаблона, заполнители могут быть заменены нашими собственными значениями, чтобы создать более подробную строку.

В соответствии с PEP 292 — Simpler String Substitutions , следующие правила применяются для использования $ sign in placeholders:

  1. $$ – это побег; он заменяется одним $
  2. $identifier называет замещающий заполнитель, соответствующий ключу сопоставления “identifier”. По умолчанию “идентификатор” должен означать идентификатор Python, как определено в http://docs.python.org/reference/lexical_analysis.html#identifiers-and-keywords . Первый неидентифицирующий символ после символа $ завершает эту спецификацию заполнителя.
  3. ${identifier} эквивалентно $identifier . Это требуется, когда допустимые символы идентификатора следуют за заполнителем, но не являются частью заполнителя, например "${noun}ification" . ( Source )

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

Мы начнем с примера того, как мы можем избежать знака $ . Предположим, мы имеем дело с валютами, и нам нужно иметь знак доллара в наших результирующих строках. Мы можем удвоить знак $ , чтобы экранировать себя в строке шаблона следующим образом:

>>> budget = Template('The $time budget for investment is $$$amount')
>>> budget.substitute(time='monthly', amount='1,000.00')
'The monthly budget for investment is $1,000.00'

Обратите внимание, что нет необходимости добавлять и дополнительное пространство между экранированным знаком и следующим заполнителем, как мы это сделали в $$$amount . Шаблоны достаточно умны, чтобы правильно избежать знака $ .

Второе правило определяет основы построения допустимого заполнителя в строках нашего шаблона. Каждый заполнитель должен быть построен с использованием символа $ , за которым следует действительный идентификатор Python. Взгляните на следующий пример:

>>> template = Template('$what, $who!')
>>> template.substitute(what='Hello', who='World')
'Hello, World!'

Здесь оба заполнителя формируются с использованием допустимых идентификаторов Python ( what и who ). Также обратите внимание, что, как указано во втором правиле, первый неидентифицирующий символ завершает заполнитель, как вы можете видеть в $who! где персонаж ! является частью не заполнителя, а конечной строки.

Могут возникнуть ситуации, когда нам нужно частично заменить слово в строке. Вот почему у нас есть второй вариант создания заполнителя. Третье правило гласит, что ${identifier} эквивалентно $identifier и должно использоваться, когда допустимые символы идентификатора следуют за заполнителем, но не являются частью самого заполнителя.

Предположим, что нам нужно автоматизировать создание файлов, содержащих коммерческую информацию о продуктах нашей компании. Файлы называются по шаблону, включающему код продукта, название и производственную партию, все они разделены символом подчеркивания ( _ ). Рассмотрим следующий пример:

>>> filename_temp = Template('$code_$product_$batch.xlsx')
>>> filename_temp.substitute(code='001', product='Apple_Juice', batch='zx.001.2020')
Traceback (most recent call last):
  ...
KeyError: 'code_'

Поскольку _ является допустимым символом идентификатора Python, наша строка шаблона работает не так, как ожидалось, и Template вызывает KeyError . Чтобы исправить эту проблему, мы можем использовать скобочную нотацию ( ${identifier} ) и построить наши заполнители следующим образом:

>>> filename_temp = Template('${code}_${product}_$batch.xlsx')
>>> filename_temp.substitute(code='001', product='Apple_Juice', batch='zx.001.2020')
'001_Apple_Juice_zx.001.2020.xlsx'

Теперь шаблон работает правильно! Это потому, что фигурные скобки правильно отделяют наши идентификаторы от символа _ . Стоит отметить, что нам нужно использовать скобочную нотацию только для кода и продукта , а не для партии , потому что . символ, следующий за batch , не является допустимым идентификатором символа в Python.

Наконец, строка шаблона хранится в свойстве template экземпляра. Давайте вернемся к Привет, Мир! пример, но на этот раз мы собираемся немного изменить шаблон :

>>> template = Template('$what, $who!')  # Original template
>>> template.template = 'My $what, $who template'  # Modified template
>>> template.template
'My $what, $who template'
>>> template.substitute(what='Hello', who='World')
'My Hello, World template'

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

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

Метод substitute()

До сих пор мы использовали метод substitute() в экземпляре Template для выполнения подстановки строк. Этот метод заменяет заполнители в строке шаблона с помощью аргументов ключевых слов или с помощью сопоставления, содержащего пары идентификатор-значение.

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

Поскольку мы рассмотрели использование аргументов ключевых слов в предыдущих примерах, давайте теперь сосредоточимся на использовании словарей. Вот пример:

>>> template = Template('Hi $name, welcome to $site')
>>> mapping = {'name': 'John Doe', 'site': 'StackAbuse.com'}
>>> template.substitute(**mapping)
'Hi John Doe, welcome to StackAbuse.com'

Когда мы используем словари в качестве аргументов с substitute() , нам нужно использовать оператор распаковки словаря: ** . Этот оператор распакует пары ключ-значение в аргументы ключевых слов, которые будут использоваться для замены соответствующих заполнителей в строке шаблона.

Распространенные Ошибки Шаблона

Есть некоторые распространенные ошибки, которые мы можем непреднамеренно ввести при использовании класса Python Template .

Например, KeyError возникает всякий раз, когда мы предоставляем неполный набор аргументов в substitute() . Рассмотрим следующий код, который использует неполный набор аргументов:

>>> template = Template('Hi $name, welcome to $site')
>>> template.substitute(name='Jane Doe')
Traceback (most recent call last):
  ...
KeyError: 'site'

Если мы вызовем substitute() с набором аргументов, который не соответствует всем заполнителям в нашей строке шаблона, то получим KeyError .

Если мы используем недопустимый идентификатор Python в некоторых наших заполнителях, то получим ValueError , сообщающий нам, что заполнитель неверен.

Возьмем этот пример, где мы используем недопустимый идентификатор $name в качестве заполнителя вместо $name .

>>> template = Template('Hi $0name, welcome to $site')
>>> template.substitute(name='Jane Doe', site='StackAbuse.com')
Traceback (most recent call last):
  ...
ValueError: Invalid placeholder in string: line 1, col 4

Только когда объект Template читает строку шаблона для выполнения подстановки, он обнаруживает недопустимый идентификатор. Он немедленно вызывает ValueError . Обратите внимание, что name не является допустимым идентификатором Python или именем, потому что оно начинается с цифры.

Метод safe_substitute()

Класс Python Template имеет второй метод, который мы можем использовать для выполнения подстановки строк. Метод называется safe_substitute() . Он работает аналогично substitute () , но когда мы используем неполный или несоответствующий набор аргументов, метод не вызывает KeyError .

В этом случае отсутствующий или несоответствующий заполнитель остается неизменным в конечной строке.

Вот как safe_substitute() работает с неполным набором аргументов ( сайт будет отсутствовать):

>>> template = Template('Hi $name, welcome to $site')
>>> template.safe_substitute(name='John Doe')
'Hi John Doe, welcome to $site'

Здесь мы сначала вызываем safe_substitute () , используя неполный набор аргументов. Результирующая строка содержит исходный заполнитель $site , но не вызывает KeyError .

Настройка класса шаблонов Python

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

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

Использование другого разделителя

Атрибут класса delimiter содержит символ, используемый в качестве начального символа заполнителей. Как мы уже видели, его значение по умолчанию равно $ .

Поскольку класс Python Template предназначен для наследования, мы можем подкласс Template и изменить значение по умолчанию delimiter , переопределив его. Взгляните на следующий пример, где мы переопределяем разделитель, чтобы использовать # вместо $ :

from string import Template
class MyTemplate(Template):
    delimiter = '#'

template = MyTemplate('Hi #name, welcome to #site')
print(template.substitute(name='Jane Doe', site='StackAbuse.com'))

# Output:
# 'Hi Jane Doe, welcome to StackAbuse.com'

# Escape operations also work
tag = MyTemplate('This is a Twitter hashtag: ###hashtag')
print(tag.substitute(hashtag='Python'))

# Output:
# 'This is a Twitter hashtag: #Python'

Мы можем использовать наш класс MyTemplate точно так же, как мы используем обычный класс Python Template . Однако теперь мы должны использовать # вместо $ для построения наших заполнителей. Это может быть удобно, когда мы работаем со строками, которые обрабатывают много знаков доллара, например, когда мы имеем дело с валютами.

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

Изменение Того, что квалифицируется как идентификатор

Атрибут id pattern class содержит регулярное выражение, которое используется для проверки второй половины заполнителя в строке шаблона. Другими словами, idpattern проверяет, что идентификаторы, которые мы используем в наших заполнителях, являются действительными идентификаторами Python. Значение по умолчанию id pattern равно r'(?-i:[_a-zA-Z][_a-zA-Z0-9]*)' .

Мы можем подклассировать Template и использовать наш собственный шаблон регулярного выражения для id pattern . Предположим, что нам нужно ограничить идентификаторы именами, которые не содержат подчеркиваний ( _ ) или цифр ( [0-9] ). Для этого мы можем переопределить шаблон id и удалить эти символы из шаблона следующим образом:

from string import Template
class MyTemplate(Template):
    idpattern = r'(?-i:[a-zA-Z][a-zA-Z]*)'

# Underscores are not allowed
template = MyTemplate('$name_underscore not allowed')
print(template.substitute(name_underscore='Jane Doe'))

Если мы запустим этот код, то получим эту ошибку:

Traceback (most recent call last):
    ...
KeyError: 'name'

Мы можем подтвердить, что цифры также не допускаются:

template = MyTemplate('$python3 digits not allowed')
print(template.substitute(python3='Python version 3.x'))

Ошибка будет:

Traceback (most recent call last):
    ...
KeyError: 'python'

Поскольку подчеркивание и цифры не включены в наш пользовательский шаблон id , объект Template применяет второе правило и разбивает заполнитель первым неидентифицирующим символом после $ . Вот почему мы получаем KeyError в каждом случае.

Построение Расширенных подклассов шаблонов

Могут возникнуть ситуации, когда нам нужно изменить поведение класса Python Template , но переопределения delimiter , idpattern или того и другого недостаточно. В этих случаях мы можем пойти дальше и переопределить атрибут pattern class, чтобы определить совершенно новое регулярное выражение для наших пользовательских подклассов Template .

Если вы решили использовать совершенно новое регулярное выражение для pattern , то вам нужно предоставить регулярное выражение с четырьмя именованными группами:

  1. escape соответствует escape-последовательности для разделителя, как в $$
  2. named соответствует разделителю и допустимому идентификатору Python, как в $identifier
  3. braced соответствует разделителю и допустимому идентификатору Python с помощью фигурных скобок, как в ${identifier}
  4. недопустимый соответствует другим неправильно сформированным разделителям, как в $site

Свойство pattern содержит скомпилированный объект регулярного выражения. Однако можно проверить исходную строку регулярного выражения, обратившись к атрибуту pattern свойства pattern . Проверьте следующий код:

>>> template = Template('$name')
>>> print(template.pattern.pattern)
\$(?:
    (?P\$) |   # Escape sequence of two delimiters
    (?P(?-i:[_a-zA-Z][_a-zA-Z0-9]*))      |   # delimiter and a Python identifier
    {(?P(?-i:[_a-zA-Z][_a-zA-Z0-9]*))}   |   # delimiter and a braced identifier
    (?P)              # Other ill-formed delimiter exprs
  )

Этот код выводит строку по умолчанию, используемую для компиляции атрибута pattern class. В этом случае мы можем ясно видеть четыре именованные группы, соответствующие регулярному выражению по умолчанию. Как было сказано ранее, если нам нужно глубоко настроить поведение Template , то мы должны предоставить эти же четыре именованные группы вместе с конкретными регулярными выражениями для каждой группы.

Запуск кода с помощью eval() и exec()

Примечание: Встроенные функции eval() и exec() могут иметь важные последствия для безопасности при использовании с вредоносным вводом. Используйте с осторожностью!

Этот последний раздел предназначен для того, чтобы открыть вам глаза на то, насколько мощным может быть класс Python Template , если мы используем его вместе с некоторыми встроенными функциями Python, такими как eval() и exec() .

Функция eval() выполняет одно выражение Python и возвращает его результат. Функция exec() также выполняет выражение Python, но никогда не возвращает его значение. Обычно вы используете exec () , когда вас интересует только побочный эффект выражения, например, измененное значение переменной.

Примеры, которые мы рассмотрим, могут показаться несколько нетрадиционными, но мы уверены, что вы можете найти несколько интересных вариантов использования этой мощной комбинации инструментов Python. Они дают представление о том, как работают инструменты, генерирующие код Python!

В первом примере мы будем использовать шаблон вместе с eval() для динамического создания списков с помощью понимания списка:

>>> template = Template('[$exp for item in $coll]')
>>> eval(template.substitute(exp='item ** 2', coll='[1, 2, 3, 4]'))
[1, 4, 9, 16]
>>> eval(template.substitute(exp='2 ** item', coll='[3, 4, 5, 6, 7, 8]'))
[8, 16, 32, 64, 128, 256]
>>> import math
>>> eval(template.substitute(expression='math.sqrt(item)', collection='[9, 16, 25]'))
[3.0, 4.0, 5.0]

Наш объект шаблона в этом примере содержит базовый синтаксис понимания списка. Начиная с этого шаблона, мы можем динамически создавать списки, заменяя заполнители допустимыми выражениями ( exp ) и коллекциями ( coll ). В качестве последнего шага мы запускаем понимание с помощью eval() .

Поскольку нет никаких ограничений на то, насколько сложными могут быть наши строки шаблонов, можно создавать строки шаблонов, содержащие любой фрагмент кода Python. Рассмотрим следующий пример использования объекта Template для создания целого класса:

from string import Template

_class_template = """
class ${klass}:
    def __init__(self, name):
        self.name = name

    def ${method}(self):
        print('Hi', self.name + ',', 'welcome to', '$site')
"""

template = Template(_class_template)
exec(template.substitute(klass='MyClass',
                         method='greet',
                         site='StackAbuse.com'))

obj = MyClass("John Doe")
obj.greet()

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

В этом случае exec() создает реальный класс и переносит его в наше текущее пространство имен. С этого момента мы можем свободно использовать класс, как и любой обычный класс Python.

Хотя эти примеры довольно просты, они показывают, насколько мощным может быть класс Python Template и как мы можем использовать его для решения сложных задач программирования на Python.

Вывод

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

В этой статье мы узнали, как работает класс Python Template . Мы также узнали о наиболее распространенных ошибках, которые мы можем ввести при использовании Template и о том, как их обойти. Наконец, мы рассмотрели, как настроить класс с помощью подклассов и как использовать его для запуска кода Python.

Имея эти знания под рукой, мы находимся в лучшем состоянии, чтобы эффективно использовать класс Python Template для выполнения интерполяции строк или подстановки в нашем коде.