MidТеория6 min

Продвинутое форматирование

f-строки с выражениями и отладочным форматом =, format_spec, string.Template и t-строки

Python предлагает несколько подходов к форматированию строк. В этой статье разберём продвинутые возможности f-строк, мини-язык форматирования, шаблоны и новые template strings из Python 3.14.

f-строки: продвинутые техники

Вложенные выражения

# Any valid expression inside braces
import math

radius = 5
print(f"Площадь круга: {math.pi * radius ** 2:.2f}")  # 78.54

# Conditional expressions
score = 85
print(f"Результат: {'сдал' if score >= 60 else 'не сдал'}")

# Method calls
name = "иван петров"
print(f"Имя: {name.title()}")  # Имя: Иван Петров

# Dictionary access
user = {"name": "Иван", "age": 25}
print(f"Имя: {user['name']}, Возраст: {user['age']}")

# List comprehension inside f-string
nums = [1, 2, 3, 4, 5]
print(f"Квадраты: {[x**2 for x in nums]}")
# Квадраты: [1, 4, 9, 16, 25]

# Walrus operator inside f-string
data = [10, 20, 30]
print(f"Сумма: {(total := sum(data))}, Среднее: {total / len(data)}")
# Сумма: 60, Среднее: 20.0

Отладочный формат (=)

# Debug format - shows expression = value
x = 42
y = 3.14
name = "Python"

print(f"{x = }")              # x = 42
print(f"{y = :.1f}")          # y = 3.1
print(f"{name = !r}")         # name = 'Python'
print(f"{name = !s}")         # name = Python
print(f"{x * y = :.2f}")     # x * y = 131.88
print(f"{len(name) = }")     # len(name) = 6

# Preserves whitespace around =
print(f"{x=}")     # x=42
print(f"{x = }")   # x = 42
print(f"{ x = }")  # x = 42 (outer spaces stripped)

# Useful for debugging complex expressions
items = {"apple": 3, "banana": 5}
print(f"{items.get('apple', 0) = }")
# items.get('apple', 0) = 3

Мини-язык форматирования (Format Specification)

Полный синтаксис спецификатора формата:

[[fill]align][sign][z][#][0][width][grouping_option][.precision][type]

Выравнивание и заполнение

text = "Python"

# Alignment: < left, > right, ^ center, = pad after sign
print(f"|{text:<20}|")     # |Python              |
print(f"|{text:>20}|")     # |              Python|
print(f"|{text:^20}|")     # |       Python       |
print(f"|{text:*^20}|")    # |*******Python*******|
print(f"|{text:.<20}|")    # |Python..............|

# Numeric alignment
n = -42
print(f"{n:>10}")    # '       -42'
print(f"{n:=10}")    # '-       42'  (pad between sign and digits)
print(f"{n:010}")    # '-000000042' (zero-fill)

Знак числа

# Sign options: +, -, space
n = 42

print(f"{n:+}")     # '+42'   (always show sign)
print(f"{-n:+}")    # '-42'
print(f"{n:-}")     # '42'    (show sign only for negative, default)
print(f"{n: }")     # ' 42'   (space for positive, minus for negative)
print(f"{-n: }")    # '-42'

# Useful for tables
values = [10, -5, 0, 23, -17]
for v in values:
    print(f"{v:+5d}")
#   +10
#    -5
#    +0
#   +23
#   -17

Числовые форматы

n = 1234567

# Grouping
print(f"{n:,}")       # '1,234,567'  (comma separator)
print(f"{n:_}")       # '1_234_567'  (underscore separator)

# Different bases
x = 255
print(f"{x:b}")       # '11111111'   (binary)
print(f"{x:o}")       # '377'        (octal)
print(f"{x:x}")       # 'ff'         (hex lowercase)
print(f"{x:X}")       # 'FF'         (hex uppercase)
print(f"{x:#b}")      # '0b11111111' (with prefix)
print(f"{x:#o}")      # '0o377'
print(f"{x:#x}")      # '0xff'

# Float formats
pi = 3.14159265

print(f"{pi:f}")       # '3.141593'   (fixed-point, default 6 decimals)
print(f"{pi:.2f}")     # '3.14'
print(f"{pi:e}")       # '3.141593e+00' (scientific notation)
print(f"{pi:.3e}")     # '3.142e+00'
print(f"{pi:g}")       # '3.14159'    (general: removes trailing zeros)
print(f"{pi:.10g}")    # '3.14159265'

# Percentage
ratio = 0.8567
print(f"{ratio:.1%}")  # '85.7%'
print(f"{ratio:.0%}")  # '86%'

# Python 3.14: z flag for negative zero
val = -0.0
print(f"{val:z.1f}")   # '0.0' (not '-0.0')

Динамические спецификаторы

# Width and precision as variables
width = 15
precision = 3
value = 3.14159

print(f"{value:{width}.{precision}f}")  # '          3.142'

# Dynamic alignment
for align in "<>^":
    print(f"{' Python ':{align}20}")

# Dynamic fill character
fill = "="
print(f"{'Title':{fill}^30}")  # ============Title=============

# Reusable format specs
money_fmt = ",.2f"
print(f"Зарплата: {50000:>{money_fmt}} руб.")  # Зарплата: 50,000.00 руб.

format() и format()

# Built-in format() function
print(format(42, "08b"))      # '00101010'
print(format(3.14, ".5f"))    # '3.14000'
print(format(1000000, ","))   # '1,000,000'

# Custom __format__ in classes
class Temperature:
    def __init__(self, celsius: float):
        self.celsius = celsius

    def __format__(self, spec: str) -> str:
        match spec:
            case "c":
                return f"{self.celsius:.1f}°C"
            case "f":
                return f"{self.celsius * 9/5 + 32:.1f}°F"
            case "k":
                return f"{self.celsius + 273.15:.1f}K"
            case _:
                return f"{self.celsius:.1f}°C"

temp = Temperature(36.6)
print(f"Температура: {temp:c}")   # Температура: 36.6°C
print(f"Температура: {temp:f}")   # Температура: 97.9°F
print(f"Температура: {temp:k}")   # Температура: 309.8K

string.Template

string.Template -- более простой и безопасный способ шаблонирования:

from string import Template

# Basic template substitution
tmpl = Template("Привет, $name! Вам $age лет.")
result = tmpl.substitute(name="Иван", age=25)
print(result)  # Привет, Иван! Вам 25 лет.

# safe_substitute - no errors for missing keys
tmpl = Template("$greeting, $name!")
result = tmpl.safe_substitute(greeting="Привет")
print(result)  # Привет, $name! (missing key kept as-is)

# Braces for ambiguous cases
tmpl = Template("${noun}ification")
print(tmpl.substitute(noun="mod"))  # modification

# Useful for user-provided templates (safer than f-strings)
user_template = Template("Заказ #$order_id: $product x $quantity")
print(user_template.substitute(
    order_id="1234",
    product="Python Book",
    quantity=2
))

Template strings (t-строки) -- Python 3.14

PEP 750 вводит новый тип строк с префиксом t"":

# t-strings create Template objects, not strings
from string.templatelib import Template, Interpolation

name = "Иван"
age = 25

# t-string syntax (identical to f-string)
tmpl = t"Привет, {name}! Вам {age} лет."
# tmpl is a Template object, NOT a string

# Template contains args - static parts and interpolations
# Process template programmatically
def render(template) -> str:
    """Render template to string."""
    parts = []
    for arg in template.args:
        if isinstance(arg, str):
            parts.append(arg)
        elif isinstance(arg, Interpolation):
            parts.append(str(arg.value))
    return "".join(parts)

print(render(tmpl))  # Привет, Иван! Вам 25 лет.

Безопасные SQL-запросы с t-строками

# Safe SQL query building with t-strings
def sql_query(template) -> tuple[str, list]:
    """Convert t-string to parameterized SQL query."""
    query_parts = []
    params = []
    for arg in template.args:
        if isinstance(arg, str):
            query_parts.append(arg)
        elif isinstance(arg, Interpolation):
            query_parts.append("?")
            params.append(arg.value)
    return "".join(query_parts), params

user_id = 42
username = "admin'; DROP TABLE users;--"

query, params = sql_query(t"SELECT * FROM users WHERE id = {user_id} AND name = {username}")
print(query)   # SELECT * FROM users WHERE id = ? AND name = ?
print(params)  # [42, "admin'; DROP TABLE users;--"]
# SQL injection prevented!

HTML-шаблонирование с t-строками

import html

def render_html(template) -> str:
    """Render t-string with HTML escaping."""
    parts = []
    for arg in template.args:
        if isinstance(arg, str):
            parts.append(arg)
        elif isinstance(arg, Interpolation):
            # Escape all interpolated values
            parts.append(html.escape(str(arg.value)))
    return "".join(parts)

user_comment = '<script>alert("XSS")</script>'
safe = render_html(t"<div class='comment'>{user_comment}</div>")
print(safe)
# <div class='comment'>&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;</div>

Сравнение подходов

name = "Иван"
age = 25

# 1. %-formatting (старый стиль, не рекомендуется)
print("Имя: %s, Возраст: %d" % (name, age))

# 2. str.format() (совместимость с Python 2)
print("Имя: {}, Возраст: {}".format(name, age))

# 3. f-strings (предпочтительный способ, Python 3.6+)
print(f"Имя: {name}, Возраст: {age}")

# 4. string.Template (пользовательские шаблоны)
from string import Template
print(Template("Имя: $name, Возраст: $age").substitute(name=name, age=age))

# 5. t-strings (Python 3.14+, безопасное шаблонирование)
# tmpl = t"Имя: {name}, Возраст: {age}"

# Performance comparison (f-strings are fastest):
# f-string  ~100ns
# format()  ~250ns
# %         ~200ns
# Template  ~1000ns

Таблица форматов чисел

# Quick reference for number formatting
n = 42
pi = 3.14159

formats = [
    (f"{n:d}",       "Десятичное целое"),
    (f"{n:05d}",     "С ведущими нулями"),
    (f"{n:+d}",      "С обязательным знаком"),
    (f"{n:b}",       "Двоичное"),
    (f"{n:o}",       "Восьмеричное"),
    (f"{n:x}",       "Шестнадцатеричное"),
    (f"{pi:.2f}",    "Фиксированная точка"),
    (f"{pi:.2e}",    "Научная нотация"),
    (f"{pi:.4g}",    "Общий формат"),
    (f"{0.856:.1%}", "Проценты"),
    (f"{1234567:,}", "С разделителем тысяч"),
]

print(f"{'Формат':<15} {'Описание':<30}")
print("-" * 45)
for fmt, desc in formats:
    print(f"{fmt:<15} {desc:<30}")

Итоги

  • f-строки -- основной инструмент форматирования (быстрый, читаемый)
  • Оператор = в f-строках показывает имя и значение выражения
  • Мини-язык форматирования: {value:fill align sign # 0 width , .precision type}
  • __format__() позволяет определить пользовательское форматирование
  • string.Template -- безопасен для пользовательских шаблонов
  • t-строки (Python 3.14) -- безопасное шаблонирование для SQL, HTML и других контекстов
  • Флаг z (Python 3.14) -- устраняет отрицательный ноль при форматировании

Проверь себя

Что выведет print(f'{x = !r}') если x = 'hello'?

Как отформатировать число 255 в шестнадцатеричном виде с префиксом 0x?

В чём преимущество string.Template перед f-строками?

Какой из способов форматирования строк самый быстрый в Python?