Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Ruff: настройка линтера и миграция

Ruff: один линтер и форматтер вместо зоопарка — настройка и миграция

Как перевести проект на Ruff: замена flake8/isort/pyupgrade/black, конфигурация в pyproject.toml, постепенное включение правил, pre-commit и редактор — практическая схема.

Прямой ответ

Ruff — линтер и форматтер на Rust, заменяющий одним инструментом flake8 с плагинами, isort, pyupgrade, black и ещё десяток утилит (проверка всего проекта — за доли секунды, что меняет культуру: линтер можно гонять на сохранение файла, а не «перед коммитом»). Миграция безболезненна: конфигурация — секция [tool.ruff] в pyproject.toml, старт — базовые наборы правил (E/F + I для сортировки импортов), автофикс (ruff check --fix) вычищает механику, спорные правила включаются постепенно. Контур здоровья: ruff в pre-commit (фреймворк), в CI (воркфлоу) и в редакторе (формат на сохранение) — три места, одна конфигурация.

Конфигурация: минимум, который работает

Базовая секция: [tool.ruff] (line-length, target-version — под минимальный поддерживаемый Python), [tool.ruff.lint] с select = ["E", "F", "I", "UP", "B"] (pycodestyle, pyflakes, isort, pyupgrade, bugbear — стартовый набор, покрывающий 80% пользы) и ignore для осознанных исключений (с комментарием «почему»). Полезные наборы для включения вторым заходом: SIM (упрощения), C4 (comprehensions), PT (pytest-стиль), RUF (собственные правила ruff), D (docstring — строгий, включать по готовности проекта; docstring-дисциплина — в статье про документацию). Per-file-ignores — точечно ("tests/**" = ["D"] — тесты без требования docstring), не глобальным ignore. ruff rule <код> объясняет любое правило с примерами — «что за E731 и зачем» выясняется за секунду.

Миграция существующего проекта

Порядок, который не устраивает бойню в диффах: (1) добавить ruff с базовыми правилами и ruff check --fix — механические фиксы отдельным коммитом (импорты, тривиальные замены); (2) ruff format — отдельным коммитом («форматирование, без семантики» — ревью пропускается, blame настраивается игнором формат-коммита); (3) включить остальные наборы с временным baseline — чинить новое, старое выжигать постепенно (# noqa: <код> с задачей, а не навсегда); (4) удалить старые инструменты (flake8/black/isort из конфигов и pre-commit) — зоопарк имеет свойство возвращаться. Споры «black против ruff format» решаются фактом: формат ruff совместим с black на ~99.9% — переход почти невидим, а скорость и один конфиг выигрывают.

Автофикс и его границы

ruff check --fix (и --unsafe-fixes осознанно) чинит механику: неиспользуемые импорты, сортировка, u-префиксы, dict-comprehension вместо dict()... Граница честности: фиксы не решают содержательных проблем (сложная функция от B008 не станет проще — правило останется красным), и «unsafe»-фиксы меняют поведение в краевых случаях (отсюда — тесты после автофиксов, как после любых правок). Привычка: ruff check --fix перед коммитом (или в pre-commit — автоматически), красное после фикса — реальная работа, не шум. Статистика правил по проекту (ruff check --statistics) показывает, где код систематически грешит — повод для разговора о стиле, а не для массового noqa.

Интеграции: pre-commit, CI, редактор

pre-commit-хук: ruff (линт+фикс) и ruff-format — локально до пуша; CI: ruff check . и ruff format --check . (без фикса — падение при неформатированном) в lint-job раньше тестов; редактор: ruff-lsp/нативная поддержка (VS Code/PyCharm) — подчёркивания на лету и формат на сохранение, autofix импортов. Один pyproject.toml — источник правды для всех трёх (версия ruff фиксируется в pre-commit-конфиге и совпадает с CI — расхождение версий = «локально зелёное, в CI красное»). Для команд: правила «что включаем» решаются один раз и меняются PR'ом в конфиг, а не локальными настройками у каждого.

Что Ruff не заменяет

Ruff — про стиль, импорты, известные баг-паттерны и часть типизационных эвристик; статический анализ типов — по-прежнему mypy/pyright (ruff не проверяет типы — связка ruff+mypy стандартна; о типизации — статья про mypy); аудит безопасности — bandit (ruff имеет частичные S-правила, но специализированный сканер глубже — безопасность кода); проверка зависимостей — pip-audit/dependabot. Правильный стек минимален: ruff (стиль/механика) + mypy (типы) + pytest (поведение) + pip-audit (уязвимости) — четыре инструмента закрывают то, что раньше требовало пятнадцати конфигов.

Практический сценарий: миграция проекта за четыре коммита

Конкретный план перевода живого проекта с зоопарка flake8+isort+black. Коммит первый — инфраструктура: в pyproject.toml появляется секция [tool.ruff] (line-length 100 — как в текущем black, target-version py311) и [tool.ruff.lint] select = ["E4", "E7", "E9", "F", "I", "UP", "B"]; в pre-commit добавляются хуки ruff и ruff-format; старые инструменты пока не удаляем (откат — удаление секции). Коммит второй — автофиксы: ruff check --fix по всему проекту; дифф — сотни мелких правок (сортировка импортов, неиспользуемые импорты, современный синтаксис) — ревьюится беглым взглядом, прогон тестов обязателен (unsafe-фиксы не применяли, но импорт мог оказаться «использованием»). Коммит третий — форматирование: ruff format . отдельным коммитом с сообщением «форматирование ruff, без семантических правок» — в git-blame настраиваем .git-blame-ignore-revs, чтобы этот коммит не заслонял авторство строк. Коммит четвёртый — уборка: из конфигов и pre-commit удаляются flake8, isort, black, pyupgrade; в CI ruff занимает их место (два шага вместо шести, время lint-job падает до секунд). Дальше — эволюция правил: через месяц команда включает SIM и PT, спорные правила обсуждаются на одном созвоне и фиксируются в конфиге PR'ом. Общее время миграции — около двух часов чистого времени, распределённых на четыре безопасных откатываемых шага.

FAQ

Ruff format совместим с black? Практически да (цель — black-совместимость, расхождения единичны и обсуждаются); проекты на black переходят одним прогоном. Обратный переход тоже возможен — форматы почти идентичны.

Какие правила включать первым делом? E4/E7/E9, F (баги и критичный стиль — дефолт), I (импорты), UP (устаревший синтаксис под target-version), B (bugbear-ловушки). Дальше — по аппетиту команды; «включить всё» в существующем проекте = тысяча ошибок в первый день.

Как быть с легаси-кодом, который не хочется трогать? Per-file-ignores на легаси-директории + правило «новые файлы — по полному набору»; # noqa — исключение с комментарием, не практика. Полный набор на новом коде окупается с первого же ревью.

Ruff быстрее flake8 «в 100 раз» — это важно? Для одного прогона — нет (секунды против минут заметны лишь на монорепозиториях). Важно следствие: линтер перестаёт быть этапом и становится фоном (сохранение файла, каждый коммит) — ошибки стиля не доезжают до ревью вовсе.

Источники: документация Ruff (конфигурация, каталог правил) — ссылки в шапке.