惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
S
Security Affairs
阮一峰的网络日志
阮一峰的网络日志
L
LangChain Blog
Microsoft Azure Blog
Microsoft Azure Blog
雷峰网
雷峰网
Recent Announcements
Recent Announcements
WordPress大学
WordPress大学
The GitHub Blog
The GitHub Blog
博客园_首页
The Cloudflare Blog
M
MIT News - Artificial intelligence
博客园 - 【当耐特】
MyScale Blog
MyScale Blog
S
SegmentFault 最新的问题
P
Proofpoint News Feed
Y
Y Combinator Blog
Jina AI
Jina AI
博客园 - 聂微东
A
About on SuperTechFans
Blog — PlanetScale
Blog — PlanetScale
博客园 - 司徒正美
G
Google Developers Blog
云风的 BLOG
云风的 BLOG
F
Full Disclosure
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Microsoft Security Blog
Microsoft Security Blog
爱范儿
爱范儿
T
Tailwind CSS Blog
J
Java Code Geeks
Vercel News
Vercel News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Stack Overflow Blog
Stack Overflow Blog
罗磊的独立博客
小众软件
小众软件
酷 壳 – CoolShell
酷 壳 – CoolShell
T
The Blog of Author Tim Ferriss
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
博客园 - 三生石上(FineUI控件)
W
WeLiveSecurity
PCI Perspectives
PCI Perspectives
Attack and Defense Labs
Attack and Defense Labs
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
宝玉的分享
宝玉的分享
IT之家
IT之家
Hacker News: Ask HN
Hacker News: Ask HN
The Register - Security
The Register - Security
T
The Exploit Database - CXSecurity.com
T
Threat Research - Cisco Blogs

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Строки в Python: всё не так просто, как кажется
bartenev_ev · 2026-05-15 · via Все публикации подряд на Хабре

Начинающим разработчикам строки обычно кажутся едва ли не самым простым, что есть в языке программирования. Возможно, причина в том, что знакомство с новым языком зачастую начинается с вывода на экран строки 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 и не теряться, когда на интервью или в проекте внезапно встретится что-то необычное.