Синтаксис Python
June 1, 2022

F-строки в Python: полный разбор. От базового синтаксиса до неочевидных трюков

Привет! Сегодня мы устроим тотальный разбор f-строк. Если ты до сих пор по привычке используешь .format() или, о ужас, старый добрый %, эта статья сэкономит тебе тонну времени и нервных клеток. А если ты уже активно юзаешь f-строки, будь готов открыть для себя пару-тройку трюков, о которых, скорее всего, даже не догадывался.

Я не буду лить воду и пересказывать официальную документацию. Вместо этого мы на реальных примерах пройдем путь от самых основ до продвинутых техник, которые сделают твой код чище, читабельнее и быстрее.

Готов? Поехали.

Почему f-строки стали стандартом? Краткий обзор альтернатив

Чтобы по-настоящему оценить мощь f-строк, нужно понимать, от чего мы ушли. До их появления в Python 3.6 у разработчиков было два основных способа форматирования. Давай быстро вспомним их и связанные с ними "боли".

Оператор %: классика и её проблемы

Это самый старый способ, унаследованный из языка C. Он использует оператор % и спецификаторы (%s для строк, %d для целых чисел и т.д.).

user = "Oleg"
processed_files = 15

# Классический пример
print("User %s has processed %d files." % (user, processed_files))
# User Oleg has processed 15 files.

На первый взгляд просто, но проблемы начинаются быстро:

  1. Порядок важен. Перепутал переменные в кортеже — получил некорректный вывод или ошибку.
  2. Читаемость страдает. Приходится постоянно бегать глазами от строки к кортежу с переменными, чтобы понять, что куда подставляется.
  3. Негибкость. Форматирование более сложных объектов, чем числа и строки, требует дополнительных ухищрений.

Метод .format(): больше гибкости, больше многословия

Метод str.format() появился в Python 2.6 и стал значительным шагом вперед. Он позволил использовать именованные аргументы и позиционные индексы, что решило проблему с порядком.

user = "Oleg"
processed_files = 15

# Стало лучше, но...
print("User {u} has processed {count} files.".format(u=user, count=processed_files))
# User Oleg has processed 15 files.

# Или так:
print("User {} has processed {} files.".format(user, processed_files))
# User Oleg has processed 15 files.

Главная проблема .format() — многословность. Нам приходится либо дублировать имена переменных (user=user), либо снова следить за их порядком. Строка все еще оторвана от данных, которые в нее подставляются. Читаемость все еще не идеальна.

f-строки: скорость, читаемость, лаконичность

И вот, в Python 3.6 (PEP 498) появляются f-строки (форматированные строковые литералы). Идея гениальна в своей простоте: вставлять выражения прямо внутрь строки.

Сравни, как та же самая задача решается с их помощью:

user = "Oleg"
processed_files = 15

# Просто, как все гениальное
print(f"User {user} has processed {processed_files} files.")
# User Oleg has processed 15 files.

Переменные находятся именно там, где они используются. Не нужно никаких .format() или %. Код становится:

  • Лаконичнее: Меньше синтаксического мусора.
  • Читабельнее: Сразу видно, что и где будет выведено.
  • Быстрее: Об этом позже, но f-строки еще и самый производительный способ форматирования.

Именно это сочетание качеств и сделало f-строки де-факто стандартом в современном Python-коде.

Базовый синтаксис и возможности

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

Основной синтаксис f-строк

Все начинается с префикса f или F прямо перед открывающей кавычкой. Этот префикс и говорит интерпретатору Python: «Внимание, это не обычная строка, а форматируемая».

Внутри строки мы можем использовать фигурные скобки {} для вставки переменных. Все, что находится внутри скобок, вычисляется на лету и превращается в строку.

name = 'Олег'
age = 1000

# Создаем f-строку с двумя переменными
message = f'Меня зовут {name}, и мне уже {age} лет.'

print(message)
# Меня зовут Олег, и мне уже 1000 лет.

Вот и вся магия. Больше никаких лишних вызовов и аргументов. Переменные вставляются прямо в текст, делая код интуитивно понятным. Можно использовать как одинарные ', так и двойные " кавычки для самой строки.

Встраивание выражений: вычисления и вызовы функций

Главный козырь f-строк в том, что внутри {} можно размещать практически любое валидное выражение Python. Это открывает массу возможностей и позволяет делать код еще компактнее.

1. Арифметические операции:

Можно выполнять математические расчеты прямо на месте.

a = 10
b = 5
print(f'Сумма {a} и {b} равна {a + b}. А их произведение: {a * b}.')
# Сумма 10 и 5 равна 15. А их произведение: 50.

2. Вызовы методов:

Вы можете применять методы к переменным прямо внутри f-строки. Это особенно удобно для форматирования текста.

log_message = 'critical error: resource not found'
print(f"Новое сообщение в логе: {log_message.upper()}")
# Новое сообщение в логе: CRITICAL ERROR: RESOURCE NOT FOUND

3. Вызовы функций:

Любую функцию, которая возвращает значение, можно вызвать внутри f-строки.

def get_db_status():
    # какая-то сложная логика проверки статуса...
    return "All systems OK"

print(f"Текущий статус базы данных: {get_db_status()}")
# Текущий статус базы данных: All systems OK

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

Спецификаторы форматирования: полный контроль над выводом

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

Синтаксис выглядит так: f'{value:specifier}'. Все самое интересное происходит после двоеточия. В этой главе мы разберем самые полезные и часто используемые спецификаторы, которые превратят твой консольный вывод из простого текста в аккуратную и читаемую таблицу данных.

Выравнивание, заполнение и обрезка строк

Это основа для создания любых псевдо-таблиц или просто красивого вывода в консоль. Мы можем указать общую ширину поля и как выравнивать в нем содержимое.

Синтаксис такой: f'{value: [fill_char] [align] [width]}'.

  • fill_char (необязательно) — символ для заполнения пустого места (по умолчанию пробел).
  • align — оператор выравнивания:
    • < — по левому краю (по умолчанию для большинства объектов).
    • > — по правому краю (по умолчанию для чисел).
    • ^ — по центру.
  • width — общая ширина поля.

Давай посмотрим на практике.

users = ["Gandalf", "Aragorn", "Gimli"]
title = "USERS"

print(f"{title:^20}") # Выравниваем заголовок по центру в поле шириной 20
print("-" * 20)      # Рисуем разделитель

for user in users:
    # Имя выравниваем по левому краю, а его длину - по правому
    print(f"{user:<10} | {len(user):>8}")

Результат в консоли будет выглядеть идеально ровно:

USERS        
--------------------
Gandalf    |        7
Aragorn    |        7
Gimli      |        5

А если мы хотим использовать другой заполнитель? Просто добавь его перед оператором выравнивания.

Generated python

status = "OK"
# Заполняем поле из 15 символов точками, выравнивая текст по центру
print(f"Status: {status:.^15}") 
# Status: ......OK.......

Этот простой набор инструментов позволяет навести идеальный порядок в текстовом выводе твоих скриптов.


Форматирование чисел: отступы, знаки и системы счисления

Работа с числами — одна из самых сильных сторон f-строк. Возможности здесь практически безграничны.

Точность для float (числа с плавающей точкой)

Это, пожалуй, самый частый кейс. Чтобы ограничить количество знаков после запятой, используется синтаксис :.Nf, где N — нужное число знаков. Важно, что f-строка не просто обрезает, а корректно округляет число.

import math
print(f"Число Пи с точностью до 2 знаков: {math.pi:.2f}")
# Число Пи с точностью до 2 знаков: 3.14

print(f"Число Пи с точностью до 5 знаков: {math.pi:.5f}")
# Число Пи с точностью до 5 знаков: 3.14159

Заполнение нулями (Padding)

Часто нужно вывести числа в формате с ведущими нулями (например, для нумерации файлов 001, 002). Для этого используется синтаксис :0N, где N — общая желаемая длина строки.

for i in range(1, 4):
    print(f"Processing file event_{i:03}.log")
# Processing file event_001.log
# Processing file event_002.log
# Processing file event_003.log

Разделители разрядов

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

large_number = 1000000000
print(f"Население планеты: {large_number:,}")
# Население планеты: 1,000,000,000

# В коде часто удобнее использовать нижнее подчеркивание
print(f"Константа: {large_number:_}")
# Константа: 1_000_000_000

Системы счисления

Нужно быстро посмотреть на число в двоичном, восьмеричном или шестнадцатеричном виде? Легко.

  • b — двоичная (binary)
  • o — восьмеричная (octal)
  • x — шестнадцатеричная (hex)
number = 255
print(f"Число {number}: bin={number:b}, oct={number:o}, hex={number:x}")
# Число 255: bin=11111111, oct=377, hex=ff

Эти инструменты покрывают 99% всех повседневных задач по форматированию чисел, делая код чистым и лишенным ручных преобразований.

Форматирование дат и времени

F-строки элегантно интегрируются с модулем datetime. Если у тебя есть объект даты или времени, ты можешь отформатировать его, используя те же коды, что и для стандартного метода .strftime().

Синтаксис: {your_date_object: <format_codes>}.

Для этого нам, конечно, понадобится сам объект datetime.

import datetime

now = datetime.datetime.now()

# Просто выводим объект "как есть"
print(f"Стандартный вывод: {now}") 
# Стандартный вывод: 2025-07-08 13:29:00.123456

# А теперь приводим к нужному формату
print(f"Дата и время в формате ISO: {now:%Y-%m-%d %H:%M:%S}")
# Дата и время в формате ISO: 2025-07-08 13:29:00

# Или в более человекочитаемом виде
print(f"Отчет за {now:%d %B %Y г. (%A)}")
# Отчет за 08 July 2025 г. (Tuesday)

Небольшой нюанс: если в вашей системе установлена русская локаль, названия месяцев и дней недели (%B, %A) могут выводиться на русском языке. В ином случае они будут на английском.

Самые частые коды, которые стоит запомнить:

  • %Y — год (4 цифры)
  • %m — месяц (01-12)
  • %d — день (01-31)
  • %H — часы (00-23)
  • %M — минуты (00-59)
  • %S — секунды (00-59)
  • %A — полное название дня недели
  • %B — полное название месяца

Полный список всех кодов можно найти в официальной документации Python по strftime() и strptime(). Знать их все наизусть не нужно, главное — помнить, что такая возможность есть, и она легко гуглится.

Продвинутые техники и неочевидные приёмы

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

Самодокументируемые выражения для отладки: f'{var=}'

Эта возможность появилась в Python 3.8 и стала настоящим подарком для всех, кто занимается отладкой.

Вспомни, как часто ты писал такой код, чтобы проверить значение переменной в какой-то точке выполнения?

# Старый способ
user_id = 101
print(f"user_id = {user_id}")
# user_id = 101

Это работает, но требует лишних телодвижений. С f-строками ты можешь получить тот же результат, просто добавив знак равенства (=) после переменной.

# Новый, элегантный способ
user_id = 101
print(f"{user_id=}")
# user_id=101

Это просто киллер-фича. Она выводит имя переменной, знак равенства и ее значение. Больше не нужно писать f'var = {var}'.

Более того, это работает с любыми выражениями, а f-строка любезно добавит пробелы для лучшей читаемости:

from math import cos, radians

angle = 60
print(f"Debug info: {angle=}, {cos(radians(angle))=}")
# Debug info: angle=60, cos(radians(angle))=0.5000000000000001

Используй f'{var=}' в своих print()-ах во время дебага, и ты больше никогда не вернешься к старым методам. Гарантирую.

Флаги конверсии: !r, !s, !a. В чем реальная разница?

По умолчанию f-строка вызывает у объекта метод __str__ (или str()), чтобы получить его строковое представление. Но иногда нам нужно другое. Для этого существуют флаги, которые ставятся сразу после переменной, но перед двоеточием форматирования.

  • !s — вызывает str() (поведение по умолчанию, используется редко, т.к. избыточно).
  • !r — вызывает repr() для получения "репрезентативного" вида объекта.
  • !a — вызывает ascii() (аналогично repr(), но экранирует все не-ASCII символы).

Разница между str() и repr() критически важна. str() — для пользователя, repr() — для разработчика.

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

some_string = "Привет"

print(f"По умолчанию: {some_string}") # Вызывается str()
print(f"С флагом !s: {some_string!s}") # То же самое, что и по умолчанию
print(f"С флагом !r: {some_string!r}") # Вызывается repr()
По умолчанию: Привет
С флагом !s: Привет
С флагом !r: 'Привет'

Видишь разницу? !r дал нам строку с кавычками. Это именно то представление, которое вы видите в интерактивной консоли Python. Оно однозначно показывает, что это — объект-строка. Это невероятно полезно при логировании и отладке, чтобы четко отличать строку "10" от числа 10.

Флаг !a нужен реже и используется для генерации строк, которые гарантированно будут содержать только ASCII-символы.

non_ascii_string = "你好, мир!"
print(f"{non_ascii_string!a}")
# '\u4f60\u597d, \u043c\u0438\u0440!'

Главный вывод: Используй !r в логах и отладочных сообщениях, чтобы получать точное, недвусмысленное представление твоих объектов.

Работа с многострочными f-строками

Когда f-строка становится слишком длинной и нарушает ограничение в 79/99 символов, ее нужно как-то перенести. Есть два основных способа, и важно понимать их отличия.

Способ 1: Тройные кавычки f"""..."""

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

name = 'Олег'
age = 1000
profession = "data scientist"

message = f"""Отчет по сотруднику:
    Имя: {name}
    Возраст: {age}
    Должность: {profession}
"""
print(message)

Результат будет именно таким, как в коде, с переносами и пробелами:

Отчет по сотруднику:
    Имя: Олег
    Возраст: 1000
    Должность: data scientist

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

Способ 2: Неявное объединение строк в скобках (рекомендованный)

Это стандартный, PEP 8-совместимый способ работы с длинными строками. Вы просто заключаете вашу конструкцию в круглые скобки. Python автоматически склеит все строковые литералы, идущие подряд.

Ключевой момент: каждая часть строки должна иметь свой собственный префикс f!

name = 'Олег'
age = 1000
profession = "data scientist"

message = (
    f"Информация о сотруднике {name.upper()}: "
    f"возраст - {age}, "
    f"должность - '{profession}'."
)
print(message)

Результат — одна строка без лишних переносов и отступов:

Информация о сотруднике ОЛЕГ: возраст - 1000, должность - 'data scientist'.

Используй первый способ для создания многострочного вывода и второй — для форматирования длинных строк в коде.

Как вывести сами фигурные скобки?

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

Правило простое: удвойте их.

  • Чтобы получить одну открывающую скобку {, напишите {{.
  • Чтобы получить одну закрывающую скобку }, напишите }}.
# Пример для генерации JSON-подобной строки
key = "user_id"
value = 123

json_string = f'{{ "{key}": {value} }}'
print(json_string)
# { "user_id": 123 }

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

А если нужно вывести двойные фигурные скобки? Угадайте... нужно их учетверить!

print(f"Конструкция в Jinja2 выглядит так: {{{{{'name'}}}}} ")
# Конструкция в Jinja2 выглядит так: {{'name'}}

Запомнить легко: чтобы вывести N скобок, напишите 2N скобок в f-строке.

Использование сложных выражений: словари и comprehensions

Мы уже знаем, что в f-строку можно вставлять выражения. Но насколько сложными они могут быть? Ответ: практически любыми.

Работа со словарями

Здесь есть один важный нюанс — кавычки. Если вы используете для f-строки одинарные кавычки, то для ключа словаря внутри нее нужно использовать двойные, и наоборот.

user_data = {
    "name": "Gandalf",
    "level": 20,
    "class": "Wizard"
}

# Правильно: f-строка в двойных кавычках, ключ словаря в одинарных
print(f"User '{user_data['name']}' has level {user_data['level']}")
# User 'Gandalf' has level 20

# Тоже правильно: f-строка в одинарных, ключ в двойных
print(f'User "{user_data["name"]}" has level {user_data["level"]}')
# User "Gandalf" has level 20

# Неправильно: кавычки совпадают
# print(f'User '{user_data['name']}'...') 
# SyntaxError: invalid syntax

List и прочие comprehensions

Вы можете встраивать даже более сложные конструкции. Например, можно создать список имен и сразу же объединить его в строку.

users = [("Aragorn", "Ranger"), ("Gimli", "Warrior")]

# Формируем список имен и соединяем его через ", "
active_users = f"Active users: {', '.join([name for name, role in users])}"
print(active_users)
# Active users: Aragorn, Gimli

Главное правило: если это можно написать в одну строку и оно возвращает значение (является выражением, а не инструкцией типа if или for), то его можно вставить в f-строку.

Это открывает невероятную гибкость, но и требует чувства меры. Слишком сложные выражения внутри f-строк могут ухудшить читаемость кода. Если логика становится громоздкой, лучше вынести ее в отдельную функцию.

Производительность и ограничения

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

Сравнение производительности

Утверждение "f-строки быстрее" — не просто слова. Это связано с механизмом их работы. F-строки вычисляются во время выполнения (runtime), но парсятся на этапе компиляции в очень эффективный байт-код. В отличие от .format() или %, здесь нет накладных расходов на вызов функций или сложный парсинг строки-шаблона.

Чтобы доказать это надежно и минимизировать влияние системного "шума", мы воспользуемся функцией timeit.repeat. Мы проведем 5 полных тестов для каждого метода (по миллиону операций в каждом) и посчитаем средний результат.

import timeit

setup_code = "name = 'Олег'; age = 1000"
num_executions = 1_000_000
num_repeats = 10

# Тест для оператора %
times_percent = timeit.repeat(
    "'%s – %s.' % (name, age)", 
    setup=setup_code, 
    number=num_executions,
    repeat=num_repeats
)

# Тест для метода .format()
times_format = timeit.repeat(
    "'{} – {}.'.format(name, age)", 
    setup=setup_code, 
    number=num_executions,
    repeat=num_repeats
)

# Тест для f-строки
times_fstring = timeit.repeat(
    "f'{name} – {age}.'", 
    setup=setup_code, 
    number=num_executions,
    repeat=num_repeats
)

# посчитаем средние значения
mean_percent = sum(times_percent) / len(times_percent)
mean_format = sum(times_format) / len(times_format)
mean_fstring = sum(times_fstring) / len(times_fstring)

print(f"Время выполнения оператора %: {mean_percent:.2f}")
print(f"Время выполнения метода .format(): {mean_format:.2f}")
print(f"Время выполнения f-строки: {mean_fstring:.2f}")

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

Время выполнения оператора %: 0.25
Время выполнения метода .format(): 0.37
Время выполнения f-строки: 0.21

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

Ключевые ограничения f-строк

Несмотря на всю свою мощь, f-строки — не серебряная пуля. У выражений внутри {} есть несколько строгих синтаксических ограничений.

1. Никаких бэкслешей \

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

# Такой код вызовет ошибку
# print(f"Newlines are represented by '\n'")
# SyntaxError: f-string expression part cannot include a backslash

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

2. Никаких комментариев #

Попытка добавить комментарий внутри выражения также приведет к SyntaxError.

# И это тоже не сработает
# print(f"Result: {10 + 5 # This is a sum}")
# SyntaxError: f-string expression part cannot include '#'

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

3. Нельзя использовать как докстринги

F-строка — это выражение, которое вычисляется в момент выполнения. Докстринг — это статическая строка, которая привязывается к объекту (функции, классу) в момент его создания. Поэтому f-строку нельзя использовать в качестве докстринга.

some_variable = "my function"
# def my_func():
#     f"""This is a docstring for {some_variable}.""" # TypeError: 'str' is not a callable
#     pass

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

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

Заключение

Мы прошли путь от истории форматирования до неочевидных трюков, которые прячутся в f-строках. Надеюсь, теперь для тебя очевидно, почему f-строки — это не просто "еще один способ", а современный, мощный и выразительный стандарт, который стоит сделать основным в своем арсенале. Они делают код чище, намерения — яснее, а выполнение — быстрее.

Если эта статья оказалась для тебя полезной, сэкономила время или научила чему-то новому, лучшей благодарностью будет поддержка проекта донатом 🤗 Спасибо, что дочитал до конца!