$ sudo teach IT
Модуль 3 · Форматирование

Настройка форматировщика

Длина строки, кавычки, отступы — настраиваем под свой стиль.

⚙️ Конфигурация 🕐 ~30 минут
🗺️

Обзор настроек форматирования

Ruff format предлагает несколько опций конфигурации, которые позволяют настроить стиль форматирования под нужды вашего проекта. Все настройки располагаются в секции [tool.ruff.format] в pyproject.toml (или [format] в ruff.toml).

В отличие от линтера, у форматтера минимальное количество настроек. Это сознательное решение: форматтер должен быть детерминированным и предсказуемым. Слишком много настроек ведёт к спорам о стиле, которых форматтеры призваны избегать.

Полная конфигурация форматирования:

pyproject.toml
[tool.ruff]
line-length = 88              # Длина строки (по умолчанию: 88)

[tool.ruff.format]
indent-width = 4              # Ширина отступа (по умолчанию: 4)
indent-style = "space"        # "space" или "tab" (по умолчанию: space)
quote-style = "double"        # "double" или "single" (по умолчанию: double)

[tool.ruff.format]
magic-trailing-comma = true   # Уважать trailing comma (по умолчанию: true)

[tool.ruff.format]
docstring-code-format = false # Форматировать код в docstrings (по умолчанию: false)
docstring-code-line-length = "dynamic"  # Длина строки в docstrings (по умолчанию: dynamic)

[tool.ruff.format]
line-ending = "auto"          # "auto", "lf", "crlf", "native" (по умолчанию: auto)
💡 Настройки форматирования можно разместить как в pyproject.toml (секция [tool.ruff.format]), так и в отдельном ruff.toml (секция [format]).
📏

line-length — длина строки

Настройка line-length (находится в секции [tool.ruff], не внутри format) определяет максимальную длину строки в символах. Ruff format будет переносить строки, превышающие этот лимит. Значение по умолчанию — 88 (как у Black).

Значение Описание Когда использовать
79 PEP 8 recommendation Строгий стандарт PEP 8
88 Black default Настройка по умолчанию, большинство проектов
100 Popular in Django Крупные проекты, где длинные имена — норма
120 Максимальная Команды, предпочитающие компактность
# pyproject.toml — line-length 79 (PEP 8)
[tool.ruff]
line-length = 79

# or 100 для больших проектов
[tool.ruff]
line-length = 100

Влияние line-length на форматирование:

# С line-length = 79:
result = some_function_with_long_name(
    arg1, arg2, arg3, arg4, arg5
)

# С line-length = 120:
result = some_function_with_long_name(arg1, arg2, arg3, arg4, arg5)
💡 Настройка line-length влияет и на линтер (правило E501), и на форматтер. Может быть полезна в больших проектах с глубокой вложенностью и длинными именами.
➡️

indent-width — ширина отступа

Настройка indent-width определяет количество пробелов для одного уровня отступа. По умолчанию — 4 (стандарт PEP 8).

# pyproject.toml
[tool.ruff.lint]  # indent-width доступен и в lint, и в format
indent-width = 4  # стандарт для Python

# или наоборот — 2 пробела:
[tool.ruff.format]
indent-width = 2  # нестандартно для Python, но возможно

Сравнение indent-width:

# indent-width = 4:
def foo():
    if True:
        for i in range(10):
            print(i)

# indent-width = 2:
def foo():
  if True:
    for i in range(10):
      print(i)
⚠️ Не рекомендуется менять indent-width на 2 в Python-проектах. PEP 8 и всё сообщество используют 4 пробела. Изменение может вызвать конфликты с другими инструментами и непонимание в команде.
↹

indent-style — пробелы или табуляция

Настройка indent-style определяет, использовать ли пробелы или табуляцию для отступов:

  • "space" (по умолчанию) — пробелы. Стандарт для Python согласно PEP 8
  • "tab" — табуляция. Редко используется в Python
# pyproject.toml
[tool.ruff.format]
indent-style = "space"  # пробелы (по умолчанию, рекомендуется)

# или
[tool.ruff.format]
indent-style = "tab"    # табуляция (нестандартно)
💡 PEP 8 рекомендует пробелы. Табуляция может использоваться для совместимости с существующим кодом.
"

quote-style — тип кавычек

Настройка quote-style определяет, какие кавычки использовать для строк: двойные (" ") или одинарные (' '). По умолчанию — double (как в Black).

Значение Пример Примечание
"double" x = "hello" Стандарт Black, PEP 8 рекомендует
"single" x = 'hello' Популярно в некоторых командах
# pyproject.toml
[tool.ruff.format]
quote-style = "single"  # одинарные кавычки

# Результат:
name = 'Alice'
greeting = f'Hello, {name}!'
items = ['one', 'two', 'three']

# С quote-style = "double" (по умолчанию):
name = "Alice"
greeting = f"Hello, {name}!"
items = ["one", "two", "three"]
💡 Если строка содержит кавычку другого типа (например, "it\'s" при quote-style = "double"), Ruff автоматически использует другой тип кавычек для этой строки, чтобы избежать экранирования.
📝

docstring-code-format и docstring-code-line-length

Опция docstring-code-format включает форматирование примеров кода внутри docstring-блоков. Это полезно, если вы документируете API с примерами использования внутри тройных кавычек.

docstring-code-line-length определяет максимальную длину строки для кода внутри docstring. Может быть:

  • "dynamic" (по умолчанию) — использует то же значение, что и общий line-length
  • Число — фиксированная длина, отличная от основной
# pyproject.toml
[tool.ruff.format]
docstring-code-format = true
docstring-code-line-length = "dynamic"  # или число, например 60

# Пример docstring до форматирования:
def calculate(a, b, c, d, e, f):
    \"\"\"Calculate something.

    Example:
        result = calculate(1, 2, 3, 4, 5, 6)
        print(f"The result is {result}")
    \"\"\"
    return a + b + c + d + e + f

Важно: Ruff форматирует только те блоки внутри docstring, которые распознаются как Python-код (начинаются с >>>, ... или являются примерами с $ python).

⚠️ docstring-code-format может изменить внешний вид примеров в документации. Убедитесь, что это подходит вашей команде.
🧐

magic-trailing-comma

Настройка magic-trailing-comma управляет поведением "волшебной завершающей запятой". Когда включено (по умолчанию), наличие запятой после последнего элемента заставляет форматировщик разбить коллекцию на несколько строк:

# C magic-trailing-comma = true (по умолчанию):
# эта запятая = форматировщик сделает многострочным
items = [
    "one",
    "two",
    "three",  # <-- trailing comma
]

# без trailing comma — может схлопнуть в одну строку
items = ["one", "two", "three"]

# С magic-trailing-comma = false:
# trailing comma игнорируется — всё схлопнется
items = ["one", "two", "three"]
💡 Это поведение унаследовано от Black. Trailing comma = явный сигнал "держи это многострочным". Отключение этой опции лишает вас тонкого контроля над форматированием.
↩️

line-ending — концы строк

Настройка line-ending определяет, какие символы конца строки использовать:

Значение Символ Когда использовать
"auto" Определяется автоматически По умолчанию
"lf" \n (Unix/macOS) Кроссплатформенные проекты
"crlf" \r\n (Windows) Windows-only проекты
"native" Системный Одно-ОС проекты
# pyproject.toml
[tool.ruff.format]
line-ending = "lf"  # всегда использовать LF (Unix-style)

# или для Windows-проекта:
[tool.ruff.format]
line-ending = "crlf"
💡 Для кроссплатформенных проектов рекомендуется line-ending = "lf" и настройка .gitattributes с * text=auto.
📋

Полная конфигурация с комментариями

# ruff.toml — полная конфигурация форматирования
line-length = 88
indent-width = 4

[format]
indent-style = "space"           # пробелы или табуляция
quote-style = "double"           # двойные или одинарные кавычки
magic-trailing-comma = true      # уважать trailing comma
docstring-code-format = true     # форматировать код в docstrings
docstring-code-line-length = "dynamic"  # или число
line-ending = "auto"             # LF, CRLF, native, auto
🎨

Примеры разных конфигураций

Конфигурация A: Стандартная (Black-совместимая)

# ruff.toml — полностью как Black
line-length = 88
indent-width = 4

[format]
indent-style = "space"
quote-style = "double"
magic-trailing-comma = true
docstring-code-format = false

Конфигурация B: Одинарные кавычки и 100 символов

# ruff.toml — для команд, предпочитающих одинарные кавычки
line-length = 100
indent-width = 4

[format]
quote-style = "single"
magic-trailing-comma = false

Конфигурация C: Строгий PEP 8

# ruff.toml — строгий PEP 8
line-length = 79
indent-width = 4

[format]
indent-style = "space"
quote-style = "double"
magic-trailing-comma = true

Конфигурация D: Документация с примерами

# ruff.toml — для проектов с богатой документацией
line-length = 88

[format]
docstring-code-format = true
docstring-code-line-length = 60
quote-style = "double"
💡 Выберите конфигурацию, которая подходит вашей команде, и зафиксируйте её в репозитории. Главное преимущество форматтера — единый стиль, а не конкретные настройки.
📓

Настройка для Jupyter Notebooks

Для форматирования Jupyter Notebooks нужна дополнительная настройка include:

# pyproject.toml
[tool.ruff]
include = ["*.py", "*.pyi", "*.ipynb"]

[tool.ruff.format]
# настройки форматтера те же самые

Ruff будет форматировать код внутри каждой ячейки ноутбука, не затрагивая метаданные и non-code ячейки.

🔍

Отладка конфигурации форматтера

Используйте ruff check --show-settings для просмотра активных настроек. Вы увидите все применённые настройки, включая те, что относятся к форматтеру:

ruff check --show-settings . | grep -A 20 "\[format\]"
# [format]
# indent-style = "space"
# indent-width = 4
# line-ending = "auto"
# magic-trailing-comma = true
# quote-style = "double"
# docstring-code-format = false
# docstring-code-line-length = "dynamic"

Если что-то работает не так, как ожидалось, проверьте:

  1. Какой конфигурационный файл используется (ruff.toml, pyproject.toml)
  2. Не переопределяются ли настройки вложенными конфигами
  3. Правильно ли указана секция (не перепутаны [tool.ruff] и [tool.ruff.format])
🤝

Совместимость с другими инструментами

Поскольку ruff format стремится быть заменой Black, он совместим с экосистемой инструментов, которые ожидают Black-стиль:

  • pre-commit — используйте ruff-format хук
  • VS Code — расширение Ruff форматирует при сохранении
  • pyproject.toml — настройки в том же файле, что и остальные инструменты
  • tox/nox — можно запускать ruff format как команду
  • CI/CD — GitHub Actions, GitLab CI, Jenkins
Инструмент Способ интеграции
pre-commit id: ruff-format
VS Code editor.defaultFormatter = "charliermarsh.ruff"
PyCharm File Watcher с ruff format
Neovim conform.nvim или null-ls
📋

Резюме настроек

Опция Секция По умолчанию Возможные значения
line-length [tool.ruff] 88 Число
indent-width [tool.ruff] 4 Число
indent-style [tool.ruff.format] "space" "space", "tab"
quote-style [tool.ruff.format] "double" "double", "single"
magic-trailing-comma [tool.ruff.format] true true, false
docstring-code-format [tool.ruff.format] false true, false
docstring-code-line-length [tool.ruff.format] "dynamic" "dynamic", число
line-ending [tool.ruff.format] "auto" "auto", "lf", "crlf", "native"
📖 Документация: docs.astral.sh/ruff/formatter/#configuration
⚖️

Подробное сравнение с Black

Хотя ruff format стремится к 100% совместимости с Black, есть несколько известных различий. Вот они с примерами:

Различие 1: Обработка пустых строк в начале функций

# Black оставляет:
def foo():

    x = 1
    return x

# Ruff format удаляет пустую строку после def:
def foo():
    x = 1
    return x

Различие 2: Форматирование trailing comma в импортах

# Black:
from typing import (
    Optional,
    Union,
)

# Ruff format то же самое — совпадает

Различие 3: Обработка однострочных if/else

# Black:
x = 1 if some_long_condition_here else 2

# Ruff format:
x = 1 if some_long_condition_here else 2
# Совпадают
💡 Полный список различий: docs.astral.sh/ruff/formatter/
📅

История и мотивация

До появления Black (2018) в Python-сообществе не было единого стандарта форматирования. Каждый проект использовал свой стиль, код-ревью часто превращались в споры о пробелах и отступах. Black изменил это, предложив "бескомпромиссный форматтер" — ты либо принимаешь его стиль полностью, либо не используешь его.

Ruff format наследует эту философию, но добавляет несколько опций для гибкости (quote-style, indent-style). Главная мотивация создания Ruff format — скорость и интеграция с линтером в одном инструменте.

Ключевые даты:

  • 2018 — Релиз Black
  • 2022 — Первый релиз Ruff (только линтер)
  • 2023 — Релиз ruff format (v0.1.0)
  • 2024 — Стабилизация ruff format, совместимость ~99% с Black
🤝

Рекомендации по настройкам для разных команд

Тип команды line-length quote-style magic-trailing-comma
Open source библиотека 88 double true
Корпоративное приложение 100 double true
Django проект 100-120 double true
Data Science / Notebooks 88 double true
🏭

Практическое задание

  1. Создайте файл с разными стилями форматирования (разные отступы, кавычки, пробелы)
  2. Настройте ruff format с quote-style = "single" и line-length = 79
  3. Запустите ruff format и проверьте результат
  4. Поменяйте настройки на quote-style = "double" и line-length = 100
  5. Снова запустите форматирование — увидите разницу
  6. Попробуйте magic-trailing-comma = false и посмотрите, как изменится форматирование коллекций
# файл для теста: test_format.py
x = {  "key1":  1,  "key2": 2 }
data = [1,2,3,4,5,6,7,8,9,10]
items = [ { "nested": True }, { "nested": False } ]
long_string = "This is a very long string that exceeds the line length limit and should be wrapped by the formatter"

def foo(x,y,z):
    return x+y+z
🤖

Программное использование ruff format

Ruff можно использовать как библиотеку из Python для программного форматирования:

import subprocess

# Форматирование одного файла
def format_file(filepath):
    result = subprocess.run(
        ["ruff", "format", filepath],
        capture_output=True, text=True
    )
    if result.returncode == 0:
        if result.stdout:
            print(f"Файл {filepath} отформатирован")
        else:
            print(f"Файл {filepath} уже отформатирован")
    else:
        print(f"Ошибка: {result.stderr}")

# Проверка без изменений
def check_formatting(filepath):
    result = subprocess.run(
        ["ruff", "format", "--check", filepath],
        capture_output=True, text=True
    )
    return result.returncode == 0

# Пакетное форматирование с прогрессом
import glob
py_files = glob.glob("**/*.py", recursive=True)
for i, f in enumerate(py_files):
    format_file(f)
    print(f"Progress: {i+1}/{len(py_files)}", end="\r")
💡 Ruff также предоставляет Python API через ruff модуль, если он установлен как библиотека: from ruff import format, check.
🔄

Если ruff format меняет слишком много

На больших проектах первый запуск ruff format может изменить тысячи файлов. Вот стратегия мягкого внедрения:

  1. Сначала посмотрите масштаб: ruff format --check . | wc -l — сколько файлов изменится
  2. Форматируйте поэтапно: по директориям: ruff format src/core/; ruff format src/api/
  3. Используйте .gitattributes: добавьте *.py diff=python для лучшего отображения diff
  4. Добавьте exclude для критических файлов: exclude = ["legacy/", "vendor/"]
  5. Запустите в отдельной ветке: git checkout -b format-all-the-things
# Стратегия поэтапного форматирования
# Шаг 1: проверить масштаб
ruff format --check . | wc -l

# Шаг 2: отформатировать основные модули
ruff format src/main_module/

# Шаг 3: проверить тесты
ruff format --check tests/

# Шаг 4: закоммитить изменения
git add -A && git commit -m "style: apply ruff format"

# Шаг 5: продолжать с остальными модулями
⚡

Бенчмарки производительности

Независимые бенчмарки показывают, что ruff format значительно быстрее Black. Вот сравнение на реальных проектах:

Проект Файлы ruff format (сек) Black (сек) Ускорение
CPython ~3,000 0.42 8.1 19x
Django ~2,000 0.31 6.4 20x
Apache Airflow ~1,500 0.25 5.2 21x
Zulip ~800 0.12 2.8 23x

Это ускорение достигается благодаря реализации на Rust, параллельной обработке файлов и эффективному AST-анализу.

✅

Чеклист внедрения ruff format в проект

  • [ ] Установить Ruff: pip install ruff или через менеджер пакетов
  • [ ] Создать конфигурационный файл (pyproject.toml или ruff.toml)
  • [ ] Настроить [tool.ruff.format] с нужными параметрами
  • [ ] Запустить ruff format . на всём проекте
  • [ ] Проверить изменения через git diff
  • [ ] Запустить тесты: pytest
  • [ ] Настроить pre-commit с ruff-format
  • [ ] Добавить ruff format --check . в CI
  • [ ] Обновить документацию проекта

Урок 3.2: Настройка форматировщика

5 вопросов