Перетяжка, Премия ТПрогер, 13.11
Перетяжка, Премия ТПрогер, 13.11
Перетяжка, Премия ТПрогер, 13.11

T-строки в Python: новый способ форматирования строк

Разбираемся, зачем они нужны и когда их использовать

83 открытий265 показов
T-строки в Python: новый способ форматирования строк

Python постоянно развивает инструменты для работы со строками. В версии 3.14 появился новый синтаксис — t-строки. Разбираемся, зачем они нужны и когда их использовать.

Эта статья написана с помощью сервис . Исходник — видео и гайд про новые t-строки в Python 3.14.

Эволюция форматирования строк в Python

Процентный стиль (%) — с самого начала

Python умел форматировать строки через процент с первых версий:

			name = "Trey"
n = 3
"%s, you have %d new messages." % (name, n)
# Результат: 'Trey, you have 3 new messages.'
		

Класс Template — Python 2.4

В модуле string появился альтернативный способ:

			python
from string import Template
t = Template("$name, you have $n new messages.")
t.substitute(name=name, n=n)
# Результат: 'Trey, you have 3 new messages.'
		

Метод format() — Python 2.6

Форматирование стало компактнее:

			"{name}, you have {n} new messages.".format(name=name, n=n)
# Результат: 'Trey, you have 3 new messages.'
		

F-строки — Python 3.6

Этот синтаксис быстро стал стандартом де-факто:

			f"{name}, you have {n} new messages."
# Результат: 'Trey, you have 3 new messages.'
		

T-строки — Python 3.14

Новый инструмент для отложенной интерполяции:

			t"{name}, you have {n} new messages."
# Результат: Template(strings=('', ', you have ', ' new messages.'), 
#            interpolations=(Interpolation('Trey', 'name', None, ''), 
#            Interpolation(3, 'n', None, '')))
		

Когда использовать t-строки

Для разработчиков приложений

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

Для авторов библиотек

T-строки полезны при разработке библиотек, где пользователям нужна отложенная интерполяция. Типичные сценарии:

  • Экранирование SQL-запросов перед выполнением
  • Обработка HTML перед рендерингом
  • Работа с регулярными выражениями
  • Предварительная обработка данных перед объединением в строку
  • Отложенная интерполяция (как в модуле logging)

Отличия f-строк от t-строк

Ключевое различие: f-строки выполняют интерполяцию немедленно и возвращают готовую строку, t-строки возвращают объект Template с отложенной интерполяцией.

Проблема, которую не решают f-строки

Рассмотрим функцию dedent из модуля textwrap, которая удаляет общие отступы из текста:

			print(dedent("""
    Look at this indented text!
    Wait... is it *not* indented anymore?
""".strip("\n")))
		

Результат:

			Look at this indented text!
Wait... is it *not* indented anymore?
		

Функция корректно убирает общие отступы, но сохраняет относительные:

			print(dedent("""\
    one
      two
        three
"""))
		

Результат:

			one
  two
    three
		

Где f-строки создают проблему

Возьмём многострочный код:

			code = r"""
def strip_each(lines):
    new_lines = []
    for line in lines:
        new_lines.append(line.rstrip("\n"))
    return new_lines
strip_each(["one\n", "two\n"])
""".strip("\n")
		

Попробуем вставить его в f-строку и передать в dedent:

			from textwrap import dedent
print(dedent(f"""\
    Example function and function call:

        {code}

    Was this indented properly?"""))
		

Получаем некорректный результат:

			Example function and function call:
        def strip_each(lines):
    new_lines = []
    for line in lines:
        new_lines.append(line.rstrip("\n"))
    return new_lines
strip_each(["one\n", "two\n"])

    Was this indented properly?
		

Первая строка кода имеет отступ, остальные — нет. Вся выходная строка обработана неправильно.

Причина проблемы

F-строка выполняет интерполяцию до того, как результат попадает в dedent. К этому моменту функция не может понять, как выглядела исходная строка до подстановки значений.

F-строка взяла строку без общих отступов (code) и поместила её внутрь строки с отступами. Когда итоговая строка попала в dedent, функция не смогла корректно определить, что нужно было сначала добавить отступы к подставляемому значению, а потом убрать общие отступы.

Функция dedent не может посмотреть историю и понять структуру исходной f-строки.

Как работают t-строки

T-строки возвращают объекты Template

F-строка возвращает готовую строку:

			name = "Trey"
f"Hello {name}"
# Результат: 'Hello Trey'
		

T-строка возвращает объект Template:

			t"Hello {name}"
# Результат: Template(strings=('Hello ', ''), 
#            interpolations=(Interpolation('Trey', 'name', None, ''),))
		

Итерация по объекту Template даёт смесь строковых фрагментов и объектов интерполяции:

			list(t"Hello {name}")
# Результат: ['Hello ', Interpolation('Trey', 'name', None, '')]
		

Применение t-строк

Объект Template позволяет библиотекам предварительно обрабатывать интерполируемые части перед финальной сборкой строки.

Решение проблемы dedent с t-строками

Используем t-строку вместо f-строки:

			code = r"""
def strip_each(lines):
    new_lines = []
    for line in lines:
        new_lines.append(line.rstrip("\n"))
    return new_lines
strip_each(["one\n", "two\n"])
""".strip("\n")

print(dedent(t"""\
    Example function and function call:
        {code}

    Was this indented properly?"""))
		

Корректный результат:

			Example function and function call:
    def strip_each(lines):
        new_lines = []
        for line in lines:
            new_lines.append(line.rstrip("\n"))
        return new_lines
    strip_each(["one\n", "two\n"])

Was this indented properly?
		

Версия dedent, работающая с t-строками, видит, что подставляемое значение имело относительные отступы внутри более крупной строки, но само содержимое не было с отступами. Функция корректно применяет отступы к подставляемому значению перед удалением общих отступов у всей строки.

Реализация dedent для t-строк

Кастомная версия dedent для работы с t-строками:

			from string import templatelib
import re
import textwrap

_INDENT_BEFORE = re.compile(r"(?m)^([ \t]+).*?(?<!{)(?:{{)*{(\d+)}")

def dedent(template: templatelib.Template) -> str:
    replacements = []
    parts = []
    n = 0
    for item in template:
        match item:
            case templatelib.Interpolation(value, _, conversion, format_spec):
                value = templatelib.convert(value, conversion)
                replacements.append(format(value, format_spec))
                parts.append("{" + str(n) + "}")
                n += 1
            case _:
                parts.append(item.replace("{", "{{").replace("}", "}}"))
    text = textwrap.dedent("".join(parts))
    for indent, n in _INDENT_BEFORE.findall(text):
        n = int(n)
        replacements[n] = textwrap.indent(replacements[n], indent).removeprefix(indent)
    return text.format(*replacements)
		

Функция выполняет следующие операции:

  1. Использует стандартный dedent из модуля textwrap
  2. Обрабатывает каждое интерполируемое значение в зависимости от его содержимого
  3. Учитывает позицию значения внутри более крупной строки
  4. Корректно применяет отступы перед финальной обработкой

Эта реализация доступна в виде библиотеки better-dedent в индексе пакетов Python:

			pip install better-dedent
		

Автор предупреждает, что библиотека может содержать баги, поэтому сообщайте о найденных проблемах в репозитории проекта.

Вывод по t-строкам: нужны или нет

Писать t-строки просто

Синтаксис t-строки идентичен f-строке, меняется только префикс:

			# F-строка
f"Hello {name}"

# T-строка
t"Hello {name}"
		

Работать с Template сложнее

Работа с объектом Template, который возвращает t-строка, требует понимания его структуры: нужно уметь итерироваться по частям и обрабатывать интерполяции.

В большинстве случаев вы не будете напрямую работать с t-строками и объектами Template в повседневном коде. Вы будете передавать их в библиотеки, которые специально разработаны для приёма t-строк.

Если библиотека поддерживает t-строки — передайте ей t-строку. Всё остальное библиотека обработает сама.

Перспективы t-строк

Python 3.14 вышел недавно, поэтому примеров использования t-строк в реальных проектах пока немного. Создатели t-строк ведут список awesome t-string для тех, кто хочет следить за развитием экосистемы.

Следите за обновлениями библиотек, которыми вы пользуетесь — возможно, скоро они начнут поддерживать t-строки для более гибкой работы со строками.

Следите за новыми постами
Следите за новыми постами по любимым темам
83 открытий265 показов