Начинающим разработчикам строки обычно кажутся едва ли не самым простым, что есть в языке программирования. Возможно, причина в том, что знакомство с новым языком зачастую начинается с вывода на экран строки 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.
То есть важно различать три уровня:
Текст как последовательность символов — это
str.Байтовое представление текста — это
bytes.Правила перехода между ними — это кодировка, например 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)

OlegZH
15.05.2026 11:42Не знал, что префиксы можно комбинировать. Я пока изучаю основу языка. А это значит, что я нахожусь на дальних подступах к современным версиям языка. Тем более, любопытно узнать, что происходит на переднем крае.
Все эти рассуждения о разновидностях строк порождают несколько вопросов.
Вопрос первый. Почему развитие языка происходит за счёт разработки новых библиотек и локальных вставок (вроде префиксов), но не за счёт введения новых синтаксических конструкций? Кажется, что обычная строка должна быть, прежде всего, "сырой" строкой (r-строкой), а любой вариант интерпретации должен предполагать уже некий формат и соответствующую синтаксическую конструкцию с участием ключевого слова
format. Строки-шаблоны крайне любопытны, и это то, что хотелось бы реализовать самостоятельно, ибо идея напрашивается сама собою.Вопрос второй. Может быть, имеет смысл ввести новый тип данных (аналог StringBuilder), который поддерживает некую структуру и управляющую информацию для сборки окончательного результата (здесь, кстати, и можно было бы указывать момент времени, когда нужно фактически вычислять/поставлять значения)?
И, наконец, вопрос третий. Неужели так трудно понять, что источником проблемы строкового представления является исходное кодирование символов? Допустим, у нас был бы символ обобщённого пробела, который по-разному интерпретировался бы в зависимости от контекста: в одних случаях этот пробел игнорируется, а в других создаётся отступ (начинающийся с символа новой строки). Другими словами, для эффективного решения всех вопросов необходимо спуститься на самый низкий уровень — уровень кодирования символов и решать все вопросы, задавая (различные) классы символов и правила их интерпретации в зависимости от контекста.
AstRonin
Хорошая статья для обучения ИИ, спасибо)
bartenev_ev Автор
Ну должен же ИИ хоть на чём-то учиться :)