Перейти к содержимому
davidka.net > 💻 🧠 Код 1001 > 📑 Шпаргалки > 🐍 Шпаргалки Python > Неочевидные возможности f-строк, о которых вы могли не знать

Неочевидные возможности f-строк, о которых вы могли не знать

Конечно! Вот та же подборка примеров с подробными пояснениями, но без префикса «Подсказка».

Базовое форматирование и произвольный код

F-строки — это современный и очень удобный способ встраивать значения переменных и выражения прямо в текст строки.

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

# Создаем две переменные с числовыми значениями.
In [1]:
one = 1
two = 2

# Вставляем значения переменных 'one' и 'two' в строку.
In [2]:
f"{one}, {two}"

# Результат — строка, где вместо переменных подставлены их значения.
Out[2]:
'1, 2'

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

# Выполняем сложение переменных прямо внутри f-строки.
In [3]:
f"{one} + {two} = {one + two}"

# Python сначала вычисляет выражение 'one + two' (получает 3), а затем вставляет результат в строку.
Out[3]:
'1 + 2 = 3'

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

# Создаем словарь для хранения цветов.
In [4]:
colors = {
    "red": "#ff0000",
    "green": "#00ff00",
    "blue": "#0000ff",
}

# Обращаемся к значению в словаре по ключу 'red'. Обратите внимание на кавычки:
# f-строка в двойных кавычках, а ключ словаря — в одинарных.
In [5]:
f"red: {colors['red']}"
Out[5]:
'red: #ff0000'

# Создаем список чисел.
In [6]:
data = [4, 8, 15, 16, 23, 42]

# Получаем доступ к 4-му и 5-му элементам списка по их индексам.
In [7]:
f"Best numbers: {data[4]} and {data[5]}"
Out[7]:
'Best numbers: 23 and 42'

Точно так же можно обращаться к атрибутам объектов (через точку .) и даже вызывать функции.

# Создаем простой класс данных и его экземпляр.
In [8]:
from dataclasses import dataclass
@dataclass
class Point:
    x: int
    y: int
pos = Point(23, 42)

# Получаем доступ к атрибутам 'x' и 'y' объекта 'pos'.
In [9]:
f"{pos.x}, {pos.y}"
Out[9]:
'23, 42'

# Вызываем встроенную функцию max() с атрибутами объекта в качестве аргументов.
In [10]:
f"bigger value: {max(pos.x, pos.y)}"
Out[10]:
'bigger value: 42'

Использование f-строк для отладки

Начиная с Python 3.8, в f-строках появился мощный инструмент для быстрой отладки.

Если после имени переменной в фигурных скобках поставить знак равенства (=), Python выведет не только значение переменной, но и её имя. Это невероятно удобно для быстрой проверки значений в коде.

In [11]:
from datetime import date
user = "eric_idle"
member_since = date(1975, 7, 31)
delta = date(2022, 4, 11) - member_since

# Используем f"{user=}" для автоматического вывода "user='eric_idle'".
In [12]:
f"{user=} {member_since=}"
Out[12]:
"user='eric_idle' member_since=datetime.date(1975, 7, 31)"

Отладочный спецификатор = можно комбинировать с обычными спецификаторами форматирования. Например, можно вывести имя переменной, а её значение отформатировать особым образом.

# {delta.days=:,d} означает:
# = -> показать имя переменной (delta.days)
# : -> начало форматирования
# ,d -> вывести целое число с запятыми в качестве разделителей тысяч
In [13]:
f"{user=!s} {delta.days=:,d}"
Out[13]:
'user=eric_idle delta.days=17,056'

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

In [15]:
from math import cos, radians
theta = 30

# Выводим как исходные данные (theta), так и результат сложного выражения.
In [16]:
f"{theta=} {cos(radians(theta))=:.3f}"
Out[16]:
'theta=30 cos(radians(theta))=0.866'

Печать отладочного представления (repr)

У объектов в Python есть два строковых представления: str() (для пользователей) и repr() (для разработчиков). По умолчанию f-строки используют str().

Чтобы управлять этим, можно использовать флаги !s (для str) и !r (для repr) прямо в f-строке. Это более короткий и удобный способ, чем вызывать функции str() или repr() вручную.

In [17]:
class Data:
    def __repr__(self):
        return '<Data ...>' # Отладочное представление
    def __str__(self):
        return 'string representation' # Пользовательское представление
data = Data()

# По умолчанию f-строка вызывает str(data)
In [18]:
f"data: {data}"
Out[18]:
'data: string representation'

# Чтобы получить отладочное представление, можно явно вызвать repr()
In [19]:
f"data: {repr(data)}"
Out[19]:
'data: <Data ...>'

# Или использовать более короткий синтаксис с флагом !r
In [20]:
f"data: {data!r}"
Out[20]:
'data: <Data ...>'

# Флаг !s делает то же, что и поведение по умолчанию
In [21]:
f"data: {data!s}"
Out[21]:
'data: string representation'

Дополнение, выравнивание и усечение

F-строки предоставляют полный контроль над форматированием вывода, включая ширину поля, выравнивание и заполнение.

После переменной ставится двоеточие :, а затем спецификатор формата.

  • > — выравнивание по правому краю
  • < — выравнивание по левому краю
  • ^ — центрирование
  • Число после символа выравнивания задает общую ширину поля.
  • Символ перед выравниванием задает, чем заполнять пустое место (по умолчанию — пробел).
In [22]:
val = "test"

# Выровнять по правому краю в поле шириной 10 символов
In [23]:
f"{val:>10}"
Out[23]:
'      test'

# Выровнять по левому краю
In [24]:
f"{val:<10}"
Out[24]:
'test      '

# Выровнять по левому краю и заполнить пустоту символами '_'
In [25]:
f"{val:_<10}"
Out[25]:
'test______'

# Центрировать
In [26]:
f"{val:^10}"
Out[26]:
'   test   '

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

In [32]:
instrument = "xylophone"

# Обрезаем строку до 5 символов
In [33]:
f"{instrument:.5}"
Out[33]:
'xylop'

Числа и их представления

Для чисел существует множество полезных форматов.

Вы можете легко конвертировать числа в двоичную (b), восьмеричную (o) или шестнадцатеричную (x/X) системы. Использование # добавляет соответствующий префикс (0b, 0o, 0x).

In [35]:
answer = 42

# Шестнадцатеричное представление
In [40]:
f"{answer:x}, {answer:X}"
Out[40]:
'2a, 2A'

# С префиксом 0x
In [42]:
f"{answer:#x}"
Out[42]:
'0x2a'

# Экспоненциальная (научная) нотация
In [44]:
f"{answer ** 8:e}"
Out[44]:
'9.682652e+12'

Часто нужно дополнить число ведущими нулями до определенной длины (например, 0042). Для этого используется 0 в качестве символа-заполнителя. Для чисел с плавающей точкой можно контролировать и общую ширину, и количество знаков после запятой.

# Дополнить до 4 знаков, используя 0 в качестве заполнителя
In [47]:
f"{answer:04d}"
Out[47]:
'0042'

# Для числа Пи: общая ширина 6, 2 знака после запятой, заполнитель 0
In [48]:
import math
f"{math.pi:06.2f}"
Out[48]:
'003.14'

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

  • +: всегда показывать знак (+42, -42).
  • (пробел): показывать пробел для положительных чисел, минус для отрицательных (42, -42). Удобно для выравнивания в столбец.
  • , или _: использовать как разделитель тысяч.
# Всегда показывать знак
In [51]:
f"{answer:+d}"
Out[51]:
'+42'

# Использовать запятую как разделитель тысяч
In [56]:
num = 1234567890
In [58]:
f"{num:,d}"
Out[58]:
'1,234,567,890'

Дополнительные темы

Многие встроенные объекты, как datetime, имеют свой собственный мини-язык форматирования, который можно использовать напрямую в f-строках.

In [60]:
from datetime import datetime
dt = datetime(2022, 4, 11, 13, 37)

# Используем стандартные коды форматирования даты и времени (%Y, %m, %d и т.д.)
In [61]:
f"{dt:%Y-%m-%d %H:%M}"
Out[61]:
'2022-04-11 13:37'

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

# Ширина и выравнивание задаются переменными
In [62]:
value = "test"
align = "^"
width = 10

# f'{value:{align}{width}}' сначала превратится в f'{value:^10}'
In [63]:
f'{value:{align}{width}}'
Out[63]:
'   test   '

# Точность для числа с плавающей точкой также задается переменной
In [64]:
prec = 2
In [65]:
f"{math.pi:.{prec}f}"
Out[65]:
'3.14'

Если вам нужно вывести в строке сами символы { или }, просто удвойте их.

In [72]:
f"Literal braces: {{value}}"
Out[72]:
'Literal braces: {value}'

Многострочные f-строки

Для длинных строк или генерации многострочного текста (например, SQL-запросов, HTML-шаблонов) используйте тройные кавычки. Выражения внутри также могут быть многострочными.

user = "hypo69"
permissions = ['read', 'write', 'execute']

# Выражение внутри f-строки перенесено на несколько строк для читаемости
report = f"""
Отчет для пользователя: {user.upper()}
----------------------------------
Разрешения: {', '.join(p for p in permissions
                            if p != 'execute')}
"""
print(report)

Результат:

Отчет для пользователя: HYPO69
----------------------------------
Разрешения: read, write

Вопросы безопасности (Очень важно!)

Это ключевой аспект, который часто упускают.

F-строки сами по себе безопасны. Когда вы пишете f»Привет, {name}», это не является уязвимостью. Python не выполняет код, который может находиться внутри переменной name. Он просто берет её строковое представление.

Опасность возникает, когда вы комбинируете f-строки с функциями, исполняющими код, такими как eval() или при работе с шаблонизаторами.

Представьте, что вы позволяете пользователю вводить свой собственный шаблон формата.

КРАЙНЕ ОПАСНЫЙ КОД — НИКОГДА ТАК НЕ ДЕЛАЙТЕ:

import os

user_template = input("Введите шаблон: ") # Пользователь вводит: '{os.system("reboot")}'
# eval() исполнит строку как код Python.
# f-строка создаст строку "f'{os.system(\"reboot\")}'", которую eval и выполнит.
eval(f'f"{user_template}"')

Если пользователь введет {os.system(«reboot»)}, ваш сервер перезагрузится. Это уязвимость удаленного выполнения кода (RCE).

Мораль: F-строки предназначены для форматирования строк, шаблоны которых пишет сам разработчик. Никогда не передавайте строки, полученные от пользователя, в eval() или другие исполняющие функции, даже через f-строки. Для пользовательских шаблонов используйте безопасные движки, такие как Jinja2.

3. Производительность: почему f-строки так быстры?

Вы уже знаете, что f-строки быстрее, чем .format() или %. Но почему?

Причина в том, что f-строка — это не функция, а часть синтаксиса Python. Когда Python компилирует ваш .py файл в байт-код, он парсит f-строку и разбивает её на две части:

  1. Литеральные части (обычный текст).
  2. Выражения для вычисления.

В байт-коде это превращается в очень эффективную последовательность инструкций: взять литерал, вычислить выражение, преобразовать его в строку, взять следующий литерал и так далее, а затем объединить все части за одну операцию (BUILD_STRING).

Метод .format() же является обычным вызовом функции. Это означает дополнительные накладные расходы на вызов метода, парсинг строки формата во время выполнения и поиск соответствий между {} и аргументами. F-строки делают большую часть этой работы во время компиляции, что и дает им преимущество в скорости.

4. Инструменты для миграции

В документе упоминались flynt и pyupgrade.

  • flynt: Это инструмент, специально созданный для автоматического преобразования старого кода с .format()и%` на f-строки. Он анализирует код и производит замену там, где это возможно.
  • pyupgrade: Более универсальный инструмент. Он не только обновляет синтаксис форматирования строк, но и применяет множество других современных синтаксических конструкций Python (например, super() без аргументов, синтаксис для аннотаций типов и т.д.).

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *