Начинающим разработчикам строки обычно кажутся едва ли не самым простым, что есть в языке программирования. Возможно, причина в том, что знакомство с новым языком зачастую начинается с вывода на экран строки Hello, world!.

«Это же просто набор символов, заключённый в кавычки!» — обычно восклицает джуниор Python-разработчик.

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

Привет, я Евгений Бартенев, автор и техлид курса «Python-разработчик» в Практикуме. В этой статье последовательно разберу, какие бывают строки в Python, чем они отличаются друг от друга, какие возможности дают и какие подводные камни могут встретиться в работе с ними. Поговорим и о базовом использовании, и о важных деталях, которые стоит помнить каждому разработчику.

Обычная строка: str

Чтобы сразу не уходить в экзотику — начнём с самой простой и часто используемой в Python строки, заключённой в одинарные или двойные кавычки.

Вообще, когда мы говорим «строка в Python», чаще всего мы имеем в виду объект типа str. Это встроенный тип данных, который определён одноимённым классом str. Иными словами, строка в Python — это не просто «кусок текста», а полноценный объект, экземпляр класса str.

Звучит, возможно, чуть более академично, чем хотелось бы в начале статьи, но это важная мысль: строки в Python — это объекты, а значит, у них есть тип, методы, поведение и особенности, о которых полезно помнить в реальной работе.

Для примеров возьмём не приевшуюся строку hello, world, а что-нибудь из жизни. Я периодически провожу мок-собеседования для Python-разработчиков и выкладываю записи на своём сайте, поэтому в качестве примера пусть будет такая строка:

s = "Мок-собеседования крайне полезны для начинающих разработчиков"

Это обычная строка — объект класса str. Проверим:

s = "Мок-собеседования крайне полезны для начинающих разработчиков"

print(type(s))

# <class 'str'>

То есть переменная s ссылается на экземпляр класса str.

При этом строку можно задать не только в двойных кавычках, но и в одинарных — с точки зрения Python это будет одно и то же:

s1 = "Мок-собеседования крайне полезны для начинающих разработчиков"
s2 = 'Мок-собеседования крайне полезны для начинающих разработчиков'

print(type(s1))
print(type(s2))

# <class 'str'>
# <class 'str'>

Обе переменные будут типа str.

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

Например, если внутри строки использованы одинарные кавычки — для обрамления строки будет удобно взять двойные:

s = "Это 'обычная' строка"

А если внутри нужны двойные кавычки, можно сделать наоборот:

s = 'Он сказал: "Начни с основ Python"'

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

s = "Он сказал: \"Начни с основ Python\""
print(s)

# Он сказал: "Начни с основ Python"

Но в большинстве случаев проще сразу подобрать подходящий тип кавычек.

Зафиксируем: обычная строка в Python — это объект типа str, предназначенный для хранения текстовых данных. Именно с такими строками разработчик работает чаще всего: получает текст от пользователя, обрабатывает данные, собирает сообщения, формирует URL, логи, SQL-запросы, HTML, JSON и многое другое.

Строка в тройных кавычках

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

s1 = """Собеседования полезно не только смотреть, но и участвовать в них"""
s2 = '''Собеседования полезно не только смотреть, но и участвовать в них'''

С точки зрения Python это объект типа str:

s1 = """Собеседования полезно не только смотреть, но и участвовать в них"""
s2 = '''Собеседования полезно не только смотреть, но и участвовать в них'''

print(type(s1))
print(type(s2))

# <class 'str'>
# <class 'str'>

То есть строка в тройных кавычках — это не какой-то отдельный строковый тип и не особый объект. Это всё тот же экземпляр класса str.

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

Например:

message = """Мок-собеседования для Python-разработчиков
на сайте boreesych.com
с разбором сильных и слабых сторон"""
print(message)

Результат:

Мок-собеседования для Python-разработчиков
на сайте boreesych.com
с разбором сильных и слабых сторон

Для сравнения, ту же самую строку в обычных кавычках пришлось бы записывать через специальный символ переноса строки \n:

message = "Мок-собеседования для Python-разработчиков\nна сайте boreesych.com\nс разбором сильных и слабых сторон"

Такой вариант тоже корректен, но он читается заметно хуже, особенно если текст длинный.

Строки в тройных кавычках часто используют в тех случаях, когда нужно сохранить форматирование текста: переносы строк, абзацы, шаблоны сообщений, фрагменты HTML, SQL-запросы, JSON, текст писем и другие многострочные конструкции.

Например:

html = """
<h1>Мок-собеседования</h1>
<p>Подготовка Python-разработчиков к реальным интервью.</p>
"""

Но, пожалуй, самое известное специальное применение строк в тройных кавычках — это docstring.

Docstring — это строка, которая размещается в начале модуля, функции, класса или метода и описывает, что именно делает этот объект.

Например:

def get_mock_info():
    """Возвращает информацию о теме мок-собеседования."""
    return "Сегодня разбираем ООП и классы"

При работе со строками в тройных кавычках важно понимать один нюанс: Python запоминает текст практически в том виде, в котором вы его написали. Из-за этого нужно быть особо внимательным: если внутри строки сделаны отступы «для красоты» — эти отступы тоже могут попасть в итоговый вывод.

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

u-строки: историческое наследие, которое всё ещё живо

В проектах, которые написаны на старых версиях Python, можно увидеть строки, перед которыми стоит символ u, например u"hello" или u"привет". На первый взгляд это выглядит как какой-то особый вид строки с дополнительным смыслом. И это впечатление не совсем обманчиво: у префикса u действительно есть своя история.

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

Начнём с примера:

s = u"На сайте boreesych.com можно записаться на мок-собеседование"

print(s)
print(type(s))

Результат вывода будет вполне знакомым:

На сайте boreesych.com можно записаться на мок-собеседование
<class 'str'>

То есть в современном Python конструкция вида u"..." создаёт самый обычный объект типа str.

Это видно и через сравнение строк:

s1 = "А ещё там есть обучающие ролики"
s2 = u"А ещё там есть обучающие ролики"

print(type(s1))
print(type(s2))
print(s1 == s2)

Результат:

<class 'str'>
<class 'str'>
True

На уровне современного Python между такими литералами в большинстве случаев нет практической разницы. И тут возникает естественный вопрос: если разницы почти нет — зачем этот префикс вообще нужен?

Причина историческая. В Python 2 существовало разделение между обычной строкой str и Unicode-строкой unicode. В этой версии языка литерал без префикса, например "abc", создавал байтовую строку, а литерал с префиксом u, например u"abc", создавал именно Unicode-строку.

Переход к Python 3 изменил модель языка: текст стал представляться типом str, а бинарные данные — типом bytes.

Когда мы пишем строку вроде:

s = "На сайте boreesych.com есть ещё и статьи на тему разработки"

мы работаем именно с текстом — с объектом типа str.

А вот если нужно представить этот текст в виде байтов, то нужно его закодировать, используя кодировку. Например, строку можно закодировать в UTF-8:

s = "На сайте boreesych.com есть ещё и статьи на тему разработки"
data = s.encode("utf-8")

print(type(s))
print(type(data))

# <class 'str'>
# <class 'bytes'>

Здесь s будет типа str, а data — типа bytes.

То есть важно различать три уровня:

  1. Текст как последовательность символов — это str.

  2. Байтовое представление текста — это bytes.

  3. Правила перехода между ними — это кодировка, например UTF-8.

Взглянем на такой пример:

s = u"На сайте boreesych.com есть опубликованные отзывы участников интервью"
data = s.encode("utf-8")

print(s)
print(data)

Здесь s — это текст, который удобно читать человеку, а data — уже байтовое представление этого текста, о чём и свидетельствует префикс b при выводе значения.

На сайте boreesych.com есть опубликованные отзывы участников интервью
b'\xd0\x9d\xd0\xb0 \xd1\x81\xd0\xb0\xd0\xb9\xd1\x82\xd0\xb5 boreesych.com \xd0\xb5\xd1\x81\xd1\x82\xd1\x8c \xd0\xbe\xd0\xbf\xd1\x83\xd0\xb1\xd0\xbb\xd0\xb8\xd0\xba\xd0\xbe\xd0\xb2\xd0\xb0\xd0\xbd\xd0\xbd\xd1\x8b\xd0\xb5 \xd0\xbe\xd1\x82\xd0\xb7\xd1\x8b\xd0\xb2\xd1\x8b \xd1\x83\xd1\x87\xd0\xb0\xd1\x81\xd1\x82\xd0\xbd\xd0\xb8\xd0\xba\xd0\xbe\xd0\xb2 \xd0\xb8\xd0\xbd\xd1\x82\xd0\xb5\xd1\x80\xd0\xb2\xd1\x8c\xd1\x8e'

По факту, в современном коде префикс u почти никогда не нужен. Обычная строка без префикса уже является Unicode-строкой. Тем не менее префикс u всё ещё можно встретить.

Во-первых, он мог сохраниться в старых кодовых базах, которые пережили миграцию с Python 2. Там такой префикс часто оставляли ради совместимости и читаемости: он явно показывал, что автор имеет дело именно с текстом, а не с байтами.

Во-вторых, его можно увидеть в статьях, старых примерах кода, обсуждениях и библиотеках, написанных в переходную эпоху между Python 2 и Python 3.

В-третьих, иногда u оставляют просто как визуальную подсказку: «это именно текст». Но в современном коде это уже больше вопрос вкуса, чем реальная необходимость.

Поэтому практический вывод здесь такой: знать про u-строки нужно обязательно, потому что они регулярно встречаются в старом коде и исторических материалах. Но активно использовать их в новом коде обычно нет смысла — стандартная строка без префикса уже делает всё, что нужно.

b-строки: уже не текст, а байты

Если u-строки интересны как исторический след в развитии Python, то b-строки — это вполне практический инструмент, с которым разработчик регулярно сталкивается и сейчас.

b"hello"
b"abc"
b"data"

На первый взгляд они очень похожи на обычные строки. Но b"..." — это не строка в привычном смысле, а объект типа bytes, последовательность байтов.

Начнём с простого примера:

data = b"Interview records: junior, middle, senior"

print(data)
print(type(data))

# b'Interview records: junior, middle, senior'
# <class 'bytes'>

В Python 3 эти сущности сознательно разведены, и смешивать их напрямую нельзя: текст нужно явно кодировать в байты, а байты — явно декодировать обратно в текст. Именно такой подход закреплён в Python 3 и в документации методов str.encode() и bytes.decode().

Причина появления b-строк напрямую связана с темой, о которой я говорил в предыдущем разделе: компьютеры в конечном счёте работают не с «символами» и «словами», а с байтами.

Когда программа читает файл в бинарном режиме, получает ответ по сети, работает с изображением, архивом, PDF, аудио, сокетом или HTTP-пакетом, она часто имеет дело не с текстом, а именно с последовательностью байтов. Для таких задач и нужен тип bytes.

PEP 3112 вводил байтовые литералы как синтаксис для записи байтовых данных: напрямую в них допускаются ASCII-символы, а произвольные байты записываются через escape-последовательности.

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

data = "Interview records: junior, middle, senior".encode("utf-8")
text = data.decode("utf-8")

print(text)
print(type(text))

# Interview records: junior, middle, senior
# <class 'str'>

При этом следует помнить про один важный момент. Обычная строка спокойно принимает кириллицу:

text = "На сайте boreesych.com есть записи интервью для разных грейдов"

А вот такой код:

data = b"На сайте boreesych.com есть записи интервью для разных грейдов"

уже не сработает.

То есть b-строка — это не «строка, где можно написать любой текст и получить байты». Нет: это именно литерал для байтов, и потому он ограничен ASCII-представлением на уровне исходного кода. Если нужен байтовый вариант, например, кириллицы, то его обычно получают через метод .encode():

text = "На сайте boreesych.com есть записи интервью для разных грейдов"
data = text.encode("utf-8")

print(data)

получим:

b'\xd0\x9d\xd0\xb0 \xd1\x81\xd0\xb0\xd0\xb9\xd1\x82\xd0\xb5 boreesych.com \xd0\xb5\xd1\x81\xd1\x82\xd1\x8c \xd0\xb7\xd0\xb0\xd0\xbf\xd0\xb8\xd1\x81\xd0\xb8 \xd0\xb8\xd0\xbd\xd1\x82\xd0\xb5\xd1\x80\xd0\xb2\xd1\x8c\xd1\x8e \xd0\xb4\xd0\xbb\xd1\x8f \xd1\x80\xd0\xb0\xd0\xb7\xd0\xbd\xd1\x8b\xd1\x85 \xd0\xb3\xd1\x80\xd0\xb5\xd0\xb9\xd0\xb4\xd0\xbe\xd0\xb2'

Есть и другие особенности. Например, при обращении по индексу строка возвращает символ, а bytes — целое число от 0 до 255.

text = "ABC"
data = b"ABC"

print(text[0])
print(data[0])

# A
# 65

То есть:

  • по индексу обычной строки возвращается символ;

  • по индексу байтовой строки возвращается числовое значение байта.

Это полезно запомнить, потому что на этом месте начинающие разработчики часто ожидают одинакового поведения.

r-строки: когда обратный слеш должен остаться обратным слешем

Предшествующие эксперименты показали, что при всей своей видимой простоте строки в Python гораздо интереснее и сложнее, чем кажутся. Продолжим изыскания: на очереди — r-строки.

Литералы с префиксом r называют raw string, то есть «сырая строка». Сразу важно зафиксировать главное: r-строка не создаёт какой-то отдельный строковый тип. Это всё тот же объект класса str.

Например:

s = r"Мок-собеседование можно пройти совершенно бесплатно"

print(s)
print(type(s))

Результат будет таким:

Мок-собеседование можно пройти совершенно бесплатно
<class 'str'>

То есть с точки зрения типа перед нами обычная строка. Но тогда возникает вопрос: зачем вообще нужен префикс r, если тип остаётся тем же самым?

Дело в том, что r-строка влияет не на тип объекта, а на то, как Python интерпретирует содержимое строкового литерала. В обычной строке символ обратного слеша \ играет специальную роль: он начинает escape-последовательность.

Например:

  • \n означает перевод строки;

  • \t означает табуляцию;

  • \\ означает сам символ обратного слеша;

  • \" позволяет вставить двойную кавычку внутрь строки.

Посмотрим на пример:

s = "Первая строка\nВторая строка"
print(s)

# Первая строка
# Вторая строка

Хотя в исходном коде мы написали \n, Python интерпретировал эту последовательность не как два отдельных символа — \ и n, — а как команду вставить перевод строки.

А вот r-строка работает иначе:

s = r"Первая строка\nВторая строка"
print(s)

# Первая строка\nВторая строка

То есть в данном случае \n сохраняется именно как два обычных символа: в raw-строках большинство escape-последовательностей не интерпретируется. Именно в этом и состоит смысл r-строк.

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

Например, в обычной строке путь к файлу придётся записать так:

path = "C:\\Users\\admin\\Documents\\interviews.txt"
print(path)

# C:\Users\admin\Documents\interviews.txt

Этот код работает, но выглядит не очень приятно: каждый обратный слеш приходится экранировать.

С r-строкой запись становится проще и нагляднее:

path = r"C:\Users\admin\Documents\interviews.txt"
print(path)

# C:\Users\admin\Documents\interviews.txt

Именно поэтому r-строки так часто используют для путей.

Ещё более характерный пример — регулярные выражения. В них обратный слеш сам по себе используется очень активно, и без r-строк код становится трудночитаемым.

Например:

pattern = "boreesych\\.com/videos/\\d+"
print(pattern)

# boreesych\.com/videos/\d+

Здесь \\d+ в коде нужен для того, чтобы в самой строке получить \d+.

А с r-строкой то же самое можно записать заметно проще:

pattern = r"boreesych\.com/videos/\d+"
print(pattern)

# boreesych\.com/videos/\d+

У r-строк есть и ограничения. Самое известное из них: r-строка не может заканчиваться одиночным обратным слешем \.

Например, вот так писать нельзя:

path = r"C:\Users\admin\"

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

f-строки: когда строка умеет подставлять значения

Внимательный читатель наверняка уже задумался: а как же f-строки? И действительно, если говорить о строках в современном Python, обойти их стороной никак нельзя. Более того, для очень многих разработчиков именно f-строки становятся основным способом работы со строками в повседневном коде.

Напомню: f-строка выглядит почти как обычная строка, только с префиксом f перед кавычками:

title = f"Автора можно отблагодарить донатом"

Магия f-строк начинается в ситуации, когда внутрь такой строки нужно подставить значение переменной или результат выражения.

Например:

level = 50
s = f"Автора можно отблагодарить донатом {level} рублей или больше"
print(s)

Результат:

Автора можно отблагодарить донатом 50 рублей или больше

Именно в этом и состоит главная идея f-строк: они позволяют вставлять значения прямо внутрь строки через фигурные скобки.

Сразу важно зафиксировать, что f-строка, как и многие другие варианты, в итоге создаёт самый обычный объект типа str.

Например:

level = 50
s = f"Автора можно отблагодарить донатом {level} рублей или больше"
print(s)
print(type(s))

Результат будет таким:

Автора можно отблагодарить донатом 50 рублей или больше
<class 'str'>

До появления f-строк значения в строки обычно вставляли через конкатенацию, метод .format() или старый %-форматинг.

Например, через конкатенацию:

level = "50"
s = "Автора можно отблагодарить донатом " + level + " рублей или больше"

Через .format():

level = 50
s = "Автора можно отблагодарить донатом {} рублей или больше".format(level)

Оба варианта рабочие, но f-строка обычно читается проще. Вместо того, чтобы собирать строку «снаружи», мы описываем её почти в том виде, в котором хотим получить результат.

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

Например:

junior_count = 12
middle_count = 8

s = f"На сайте boreesych.com доступно {junior_count + middle_count} записей для junior и middle"

Или так:

level = "junior"
s = f"Уровень интервью: {level.upper()}"
print(s)

Результат:

Уровень интервью: JUNIOR

Это делает f-строки особенно удобными: они позволяют не просто «вставить значение», а сразу подготовить его к выводу.

Кроме простой подстановки, f-строки умеют форматировать значения. Например, можно красиво выводить числа:

rating = 4.87654
s = f"Средняя оценка интервью: {rating:.2f}"
print(s)

# Средняя оценка интервью: 4.88

Здесь :.2f означает, что число нужно вывести с двумя знаками после запятой.

Можно выравнивать текст:

level = "junior"
print(f"|{level:>10}|")

# |    junior|

Можно добавлять разделители разрядов:

views = 12500
print(f"Количество просмотров записей: {views:,}")

# Количество просмотров записей: 12,500

У f-строк есть ещё одна удобная особенность, которая особенно нравится многим разработчикам при отладке.

Можно написать так:

level = "senior"
count = 3

print(f"{level=}, {count=}")

Результат будет таким:

level='senior', count=3

Это удобно, если нужно быстро посмотреть и имя переменной, и её значение.

Поскольку f-строки используют фигурные скобки для подстановки выражений, возникает вопрос: что делать, если в самой строке нужно вывести символы { и }?

Для этого их нужно удвоить:

name = "Python"
s = f"{{name}} = {name}"
print(s)

# {name} = Python

У f-строк есть и ограничения.

Во-первых, f-строка вычисляется сразу в момент создания. Это не «ленивый шаблон», который потом можно отдельно применить к данным, а полностью готовая строка.

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

Например, вот так уже читается тяжеловато:

user = {"level": "junior", "count": 7}
s = f"На сайте boreesych.com есть {user['count']} записей уровня {user['level'].upper()}"
print(s)

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

t-строки: шаблон до того, как он стал обычной строкой

А вот теперь начинается самое интересное. В новых версиях Python появились ещё и t-строки — template strings.

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

Посмотрите на этот пример:

level = "junior"
f_value = f"На сайте boreesych.com есть записи интервью уровня {level}"
template = t"На сайте boreesych.com есть записи интервью уровня {level}"

print(f_value)
print(template)
print(type(template))

# На сайте boreesych.com есть записи интервью уровня junior
# Template(strings=('На сайте boreesych.com есть записи интервью уровня ', ''), interpolations=(Interpolation('junior', 'level', None, ''),))
# <class 'string.templatelib.Template'>

Здесь видно, что типом t-строки будет уже не str, а объект шаблона Template из модуля string.templatelib. Именно так t-строки и задуманы: они дают доступ к структуре шаблона до того, как всё будет объединено в обычную строку. Это и есть главное отличие от f-строк.

При этом t-строки не являются ленивыми шаблонами в духе string.Template: выражения внутри фигурных скобок вычисляются сразу, как и в f-строках.

Особенность f-строк в том, что они слишком быстро превращаются в готовый текст. Это удобно, пока нам просто нужно красиво собрать сообщение. Но иногда разработчику важно не сразу получить финальную строку, а сначала посмотреть, что именно было подставлено, проверить значения, экранировать их, отфильтровать или обработать по специальным правилам. Именно для этого и появились t-строки.

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

Здесь есть важный практический момент: t-строки появились только в Python 3.14. Это значит, что в большинстве существующих проектов, статей, курсов и продакшен-кодовых баз вы почти не встретите t-строк. В проектах, написанных на Python 3.12 или 3.13, такого синтаксиса ещё просто нет.

Строки с комбинированными префиксами

К этому моменту может возникнуть вполне естественный вопрос: если в Python есть разные строковые префиксы, можно ли их комбинировать между собой?

Да, но не любые. В Python 3.14 и новее r можно комбинировать с f, t и b: допустимы такие варианты, как fr, rf, tr, rt, br и rb. Порядок букв здесь роли не играет: такие варианты эквивалентны.

Префиксы регистронезависимы: например, RF и rf работают одинаково.

Сразу видно важную закономерность: почти все полезные комбинации строятся вокруг r. Это не случайно. Сам по себе r не задаёт новый тип строки, а лишь меняет способ интерпретации литерала — в первую очередь поведение обратного слеша. Поэтому его и можно «накладывать» на другие виды строк.

Таким образом, разговор о комбинированных префиксах — это, по сути, разговор о том, как raw-режим сочетается с другими возможностями строк.

Самая известная комбинация — это fr-строки. Такая строка сочетает сразу две идеи:

  • это форматируемая строка, как f-строка;

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

Например:

level = "junior"
s = rf"Путь к записям интервью: C:\recordings\{level}"

Здесь подстановка {level} работает как в f-строке, а обратные слеши в текстовой части не требуют экранирования, как в обычной строке.

Именно поэтому rf-строки особенно любят там, где нужно одновременно и подставлять значения, и работать со строками, содержащими много обратных слешей. Самый типичный пример — регулярные выражения.

Например:

prefix = "interview"
pattern = rf"{prefix}\d+"

Ещё одна допустимая комбинация — br или rb. Такие литералы создают объект типа bytes, но при этом используют raw-режим записи, то есть позволяют удобнее работать с обратными слешами внутри байтового литерала.

Например:

data = br"\x50\x79\x74\x68\x6f\x6e"
print(data)
print(type(data))

# b'\\x50\\x79\\x74\\x68\\x6f\\x6e'
# <class 'bytes'>

Подобный синтаксис встречается реже, чем rf-строки, но он бывает полезен при работе с бинарными шаблонами, протоколами или регулярными выражениями в байтовом виде.

В новых версиях Python допустимы также tr и rt — комбинация raw-режима с t-строками.

Идея здесь та же, что и в случае с rf:

  • t отвечает за шаблонную природу литерала;

  • r делает запись строковой части удобнее там, где много обратных слешей.

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

Важно помнить о том, что комбинировать можно далеко не всё.

Например, t-строки нельзя комбинировать с u или b. То есть вариантов вроде tb или ut быть не должно.

Иными словами, комбинированные строковые префиксы в Python строятся вокруг r.

Если задуматься, идея здесь довольно изящная. В Python есть префиксы, которые отвечают за «природу» литерала:

  • b — байты;

  • f — форматируемая строка;

  • t — шаблонная строка.

А r отвечает не за природу значения, а за способ записи строкового литерала в исходном коде. Именно поэтому он и может сочетаться с другими префиксами: он не конкурирует с ними, а дополняет их.

d-строки: возможно, следующий новичок в семействе строк

На этом основную часть экскурсии по зоопарку строковых литералов Python можно считать законченной. Но язык продолжает развиваться, и время от времени появляются идеи новых строковых префиксов. Одна из самых заметных таких идей последних лет — это d-строки.

На момент написания этой статьи d-строки — это пока не часть стабильного Python, а предложение, оформленное в виде PEP 822. Сейчас этот PEP имеет статус Draft, то есть идея обсуждается, но ещё не стала частью языка. В самом PEP указана ориентировочная цель — Python 3.15.

Если говорить коротко, то идея d-строк очень проста: добавить специальный префикс d для многострочных строк, который автоматически убирал бы общий отступ. В тексте PEP это описано буквально как механизм, автоматически удаляющий indentation из multiline string literals, а сам префикс d расшифровывается как dedent.

То есть задумка примерно такая:

def get_html():
    return d"""
        <section>
            <h1>Записи интервью</h1>
            <p>На сайте boreesych.com есть записи интервью разного уровня.</p>
        </section>
    """

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

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

  • либо пишет многострочную строку без отступов, и тогда она визуально выбивается из структуры кода;

  • либо оставляет естественные отступы, но потом получает лишние пробелы в строке;

  • либо оборачивает литерал в textwrap.dedent(...), что делает запись длиннее и менее удобной.

Именно эти неудобства PEP 822 и пытается устранить. В обосновании прямо говорится, что существующие варианты проигрывают по читаемости и удобству сопровождения, а textwrap.dedent() ещё и добавляет runtime overhead.

Если задуматься, то d-строки — это, по сути, попытка встроить идею textwrap.dedent() прямо в синтаксис языка. Причём авторы предложения подчёркивают, что это особенно полезно именно для литералов: интерпретатор может убрать отступы сразу на уровне синтаксиса, без дополнительного вызова функции во время выполнения.

Ещё один интересный момент в PEP 822 состоит в том, что d-префикс предлагается не как изолированная экзотика, а как механизм, сочетающийся с уже существующими подходами. В тексте PEP прямо сказано, что d можно комбинировать с f, t, r и b, причём порядок букв не принципиален, а регистр, как и у других строковых префиксов, не имеет значения.

Это значит, что в будущем могли бы появиться такие варианты, как:

  • df — dedented f-string;

  • dr — dedented raw string;

  • dt — dedented template string;

  • db — dedented bytes literal.

То есть сама идея развивается вполне в духе уже существующей системы строковых префиксов Python.

При этом не надо думать, будто d-строки вот-вот неизбежно появятся в языке. Пока это всё ещё черновик предложения, а значит, итог может быть разным: PEP могут принять, переработать, отложить или вовсе отклонить. Поэтому говорить о d-строках лучше не как о «новом типе строк Python», а как об интересной и вполне логичной идее, за которой стоит следить.

Заключение

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

Если вынести из всей статьи одну главную мысль, то она будет довольно простой: строка в Python — это не просто «кусок текста», а полноценный инструмент, у которого есть разные режимы записи, разные сценарии использования и свои ограничения. И чем лучше разработчик понимает эти различия, тем чище, понятнее и надёжнее становится его код.

Строго говоря, не всё из перечисленного — отдельные типы строк. Часть вариантов — это разные формы строковых литералов, часть — префиксы, влияющие на разбор кода, а bytes вообще отдельный тип для байтовых данных.

В повседневной практике вам, скорее всего, чаще всего будут встречаться обычные строки, строки в тройных кавычках, r- и f-строки. Но знать про остальные варианты тоже полезно — хотя бы для того, чтобы уверенно читать чужой код, понимать исторический контекст Python и не теряться, когда на интервью или в проекте внезапно встретится что-то необычное.

Комментарии (3)


  1. AstRonin
    15.05.2026 11:42

    Хорошая статья для обучения ИИ, спасибо)


    1. bartenev_ev Автор
      15.05.2026 11:42

      Ну должен же ИИ хоть на чём-то учиться :)


  1. OlegZH
    15.05.2026 11:42

    Не знал, что префиксы можно комбинировать. Я пока изучаю основу языка. А это значит, что я нахожусь на дальних подступах к современным версиям языка. Тем более, любопытно узнать, что происходит на переднем крае.

    Все эти рассуждения о разновидностях строк порождают несколько вопросов.

    Вопрос первый. Почему развитие языка происходит за счёт разработки новых библиотек и локальных вставок (вроде префиксов), но не за счёт введения новых синтаксических конструкций? Кажется, что обычная строка должна быть, прежде всего, "сырой" строкой (r-строкой), а любой вариант интерпретации должен предполагать уже некий формат и соответствующую синтаксическую конструкцию с участием ключевого слова format. Строки-шаблоны крайне любопытны, и это то, что хотелось бы реализовать самостоятельно, ибо идея напрашивается сама собою.

    Вопрос второй. Может быть, имеет смысл ввести новый тип данных (аналог StringBuilder), который поддерживает некую структуру и управляющую информацию для сборки окончательного результата (здесь, кстати, и можно было бы указывать момент времени, когда нужно фактически вычислять/поставлять значения)?

    И, наконец, вопрос третий. Неужели так трудно понять, что источником проблемы строкового представления является исходное кодирование символов? Допустим, у нас был бы символ обобщённого пробела, который по-разному интерпретировался бы в зависимости от контекста: в одних случаях этот пробел игнорируется, а в других создаётся отступ (начинающийся с символа новой строки). Другими словами, для эффективного решения всех вопросов необходимо спуститься на самый низкий уровень — уровень кодирования символов и решать все вопросы, задавая (различные) классы символов и правила их интерпретации в зависимости от контекста.