Опирается на правила:
PY-5.1…PY-5.3,PY-RUFF-1…PY-RUFF-3из Python Style Guide → раздел 5. Форматирование.
Важно знать
ruff format— единственный форматтер; руками не выравнивать.- Длина строки — единая по проекту:
88(дефолтruff) либо120(командный override); фиксируется вpyproject.toml.- Горизонтальное выравнивание присваиваний и словарей запрещено — шум в diff при переименовании.
- Отступ — 4 пробела; табы запрещены.
- Перенос по бинарному оператору — перед оператором (Knuth-style:
+,andв начале новой строки).- Висячая запятая в многострочных литералах обязательна —
ruff formatставит её автоматически.pyproject.toml— единственное место конфигурации; неsetup.cfg, не.flake8.ruff check+ruff format --check+mypy— три прогона CI; fail при любом нарушении.
Форматирование не должно занимать времени на ревью. Один раз зафиксировали ruff format в pyproject.toml и CI — дальше формат всегда правильный, без споров «куда поставить запятую».
ruff format — не выравнивать вручную
PY-5.1: форматирование делает ruff format, не разработчик.
ruff format src/ tests/
ruff format --check src/ tests/
ruff format совместим с black: одинаковые правила расстановки кавычек (двойные), trailing comma, переноса длинных выражений. Переключаться между ними без изменений в коде не нужно — на проектах с историей black переход на ruff format прозрачен.
Ручные выравнивания после ruff format — нарушение PY-5.1: при следующем прогоне форматтер их откатит, порождая ложный diff.
Длина строки
PY-5.2: длина строки — единая в проекте и зафиксированная в pyproject.toml.
[tool.ruff]
line-length = 88
Дефолт ruff format — 88 символов (наследство black). Команды с длинными именами доменных объектов или несколькими дженериками часто выбирают 120 — это командный override, не «как получилось»:
[tool.ruff]
line-length = 120
def find_active_orders_by_customer(
customer_id: UUID,
status: OrderStatus,
page: int = 0,
) -> list[Order]: ...
При превышении лимита рефакторим структуру, не подбираем ширину под существующий код:
- Длинная сигнатура → возможно, метод делает слишком много или принимает слишком много параметров.
- Длинный вызов → multi-line с висячей запятой.
- Длинная цепочка
.method()→ разбить на промежуточные переменные.
Перенос длинных выражений
PEP 8 и ruff format определяют три устойчивых паттерна.
Многострочный вызов с висячей запятой
order = Order(
customer_id=customer_id,
items=items,
status=OrderStatus.PENDING,
created_at=datetime.now(UTC),
)
Открывающая скобка — на строке вызова. Каждый аргумент — отдельная строка. Висячая запятая после последнего аргумента — ruff format ставит её автоматически, diff при добавлении аргумента будет однострочным.
Перенос перед бинарным оператором (Knuth-style)
is_eligible = (
order.status == OrderStatus.CONFIRMED
and order.amount >= MINIMUM_REFUND_AMOUNT
and not order.is_refunded
)
Оператор в начале новой строки — сигнал «продолжение выражения». Оператор в конце предыдущей строки легко пропустить при скане сверху вниз.
total = (
product.base_price
+ product.tax_amount
- discount.value
)
Длинная цепочка вызовов
confirmed_orders = (
session.query(Order)
.filter(Order.status == OrderStatus.CONFIRMED)
.filter(Order.customer_id == customer_id)
.order_by(Order.created_at.desc())
.all()
)
Каждый .method() — на отдельной строке с отступом 4 пробела. Цепочка читается сверху вниз, каждый шаг виден.
Горизонтальное выравнивание запрещено
PY-5.3: не выравнивать присваивания и словари горизонтально.
customer_id = UUID("...") # ✗ — выравнено пробелами
order_status = OrderStatus.PENDING
total_amount = Decimal("0.00")
customer_id = UUID("...") # ✓
order_status = OrderStatus.PENDING
total_amount = Decimal("0.00")
config = {
"host": "localhost", # ✗ — выравнено двоеточие
"port": 5432,
"database": "orders",
}
config = {
"host": "localhost", # ✓
"port": 5432,
"database": "orders",
}
Почему не выравнивать:
- Diff-noise при переименовании. Добавили
payment_reference_id— нужно переформатировать все остальные строки, чтобы выравнивание сохранилось. Diff показывает N изменений вместо 1. ruff formatоткатывает. Форматтер не сохраняет горизонтальное выравнивание — при следующем прогоне «красивый» блок рассыплётся.- Субъективность. Кто-то выравнивает по самому длинному имени текущего блока, кто-то — по следующему полю. Единое правило «без выравнивания» снимает вопрос навсегда.
Конфигурация в pyproject.toml
PY-RUFF-1: конфиг ruff — только в pyproject.toml, не в разрозненных .flake8/.isort.cfg/setup.cfg.
[tool.ruff]
line-length = 88
target-version = "py312"
[tool.ruff.lint]
select = ["E", "W", "F", "I", "N"]
ignore = []
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
PY-RUFF-3: все три прогона в CI — блокируют merge при нарушении:
- run: ruff check src/ tests/
- run: ruff format --check src/ tests/
- run: mypy src/
Отключение правил ruff без обоснования — нарушение PY-RUFF-X1. Если правило реально мешает конкретному случаю:
result = long_function_name(arg_one, arg_two) # noqa: E501 # justify: URL в строке, не сокращать
# noqa только с кодом правила и justify-комментарием (PY-7.X2).
Что запрещено
| Антипаттерн | Правило | Что взамен |
|---|---|---|
Ручное форматирование вместо ruff format | PY-5.1 | ruff format src/ |
| Разная длина строки в разных файлах проекта | PY-5.2 | единый line-length в pyproject.toml |
| Горизонтальное выравнивание присваиваний | PY-5.3 | без выравнивания |
| Горизонтальное выравнивание значений словаря | PY-5.3 | без выравнивания |
# noqa без кода правила и justify | PY-RUFF-3, PY-7.X2 | # noqa: E501 # justify: ... |
Конфиг ruff в .flake8 или setup.cfg | PY-RUFF-1 | pyproject.toml [tool.ruff] |
| Отключение правил ruff «потому что мешают» | PY-RUFF-X1 | обсудить и зафиксировать override |
Оператор +/and/or в конце строки при переносе | PEP 8 A.4 | оператор в начале новой строки |
Куда дальше
- Именование —
snake_case,PascalCase, константы, приватность. - Импорты — абсолютные импорты, группировка stdlib/third-party/local, запрет wildcard.
- Тайп-хинты —
X | NoneвместоOptional,Protocolдля портов,Decimalдля денег. - Enforcement через ruff и mypy — полная конфигурация CI, suppression-политика.
- Раздел «Форматирование» в Python Style Guide — нормативные формулировки
PY-5.*.