Ruff против Flake8 и Black: почему стоит перейти на один инструмент
Сравниваем популярные линтеры и форматтеры Python по скорости, гибкости настройки и удобству интеграции в CI. Показываем, как за пять минут подключить Ruff к реальному проекту.
Содержание
Ruff против Flake8 и Black: почему стоит перейти на один инструмент
Если вы работаете с Python дольше нескольких месяцев, у вас наверняка сложился привычный набор инструментов для поддержания качества кода: Flake8 проверяет стиль и ловит очевидные ошибки, Black форматирует всё по единому стандарту, isort расставляет импорты по правилам, pylint добавляет более глубокий анализ. Каждый инструмент делает своё дело хорошо — но вместе они превращаются в громоздкий стек с отдельными конфигурационными файлами, разными скоростями работы и периодическими конфликтами между собой.
В 2022 году Чарли Марш выпустил Ruff — линтер и форматтер, написанный на Rust, который претендует на замену всего этого зоопарка. За два года проект набрал более 35 тысяч звёзд на GitHub и был принят в production такими командами, как Astral, Pydantic, FastAPI и Hugging Face. Разберёмся, чем Ruff заслужил такое доверие и когда его действительно стоит использовать вместо привычных инструментов.
Что умеет каждый из инструментов
Прежде чем сравнивать, стоит честно зафиксировать, что именно делает каждый участник.
Flake8
Flake8 — это обёртка над тремя инструментами: pycodestyle (бывший pep8), pyflakes и mccabe. Он проверяет соответствие кода PEP 8, ловит неиспользуемые импорты, неопределённые переменные и считает цикломатическую сложность функций. Богатая экосистема плагинов (flake8-bugbear, flake8-comprehensions, flake8-annotations и десятки других) позволяет расширять проверки практически до бесконечности.
Главный минус — скорость. Flake8 написан на чистом Python и при анализе крупного проекта работает заметно медленно. На репозитории с 50 000 строк кода он легко тратит 10–15 секунд.
Black
Black позиционирует себя как «бескомпромиссный форматтер». Он не спрашивает мнения разработчика о том, как расставлять кавычки или переносить длинные строки — просто применяет единый стиль. Это одновременно его сила и слабость: конфигурировать практически нечего, зато в команде исчезают споры о форматировании.
Black работает быстро, но только форматирует — он не ловит логические ошибки, не проверяет импорты и не занимается анализом кода.
isort
isort сортирует импорты по группам (стандартная библиотека, сторонние пакеты, локальные модули) и внутри групп — по алфавиту. Звучит просто, но конфигурация isort нередко конфликтует с Black: у них разные представления о том, как должна выглядеть строка импорта после форматирования. Решение — флаг --profile black, но об этом нужно помнить и явно прописывать.
pylint
Pylint — самый тяжёлый из перечисленных. Он делает глубокий статический анализ: проверяет типы там, где может, ловит потенциальные ошибки в логике, анализирует наследование. За это платишь скоростью: pylint на том же проекте в 50 000 строк может работать минуту и более.
Ruff: что это такое и почему он такой быстрый
Ruff написан на Rust — это объясняет большую часть его преимуществ. Он не вызывает Python-интерпретатор для каждого файла, работает параллельно на всех ядрах процессора и использует эффективные структуры данных для хранения AST.
Но скорость — не единственное, что отличает Ruff. Ключевое архитектурное решение: авторы переписали правила из Flake8, flake8-bugbear, flake8-comprehensions, isort, pyupgrade и нескольких других инструментов прямо внутри Ruff. Это означает, что вместо пяти отдельных процессов вы запускаете один — и он делает всё то же самое.
На момент написания статьи Ruff реализует более 800 правил, охватывающих:
- стиль PEP 8 (E, W серии)
- pyflakes-проверки (F серия)
- flake8-bugbear (B серия)
- правила для аннотаций типов (ANN серия)
- проверки безопасности из bandit (S серия)
- правила isort (I серия)
- и многое другое
Начиная с версии 0.1.0, Ruff также включает собственный форматтер, совместимый с Black по стилю вывода. Это означает, что теоретически можно убрать и Black из стека.
Сравнение по ключевым параметрам
Скорость
Это самый очевидный и измеримый параметр. Авторы Ruff публикуют бенчмарки на репозитории CPython (около 1 800 файлов):
| Инструмент | Время (cold cache) |
|---|---|
| Flake8 | ~16 с |
| pylint | ~60+ с |
| Ruff | ~0.3 с |
Разница в 50–100 раз — не маркетинговое преувеличение. Это реально ощутимо в pre-commit хуках, когда вы хотите получить фидбек ещё до того, как успели отвлечься.
Конфигурация
Flake8 использует .flake8 или секцию [flake8] в setup.cfg. Black читает pyproject.toml. isort — отдельный [isort] блок. Ruff читает только pyproject.toml или ruff.toml, и все настройки собраны в одном месте.
Пример минимальной конфигурации в pyproject.toml:
[tool.ruff]
line-length = 88
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "W", "B", "I", "UP"]
ignore = ["E501"]
[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["S101"] # разрешаем assert в тестах
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
Здесь select перечисляет группы правил: E/W/F — это классический Flake8, B — bugbear, I — isort, UP — pyupgrade (автоматическое обновление синтаксиса до целевой версии Python).
Автофикс
Это одна из самых практически полезных функций Ruff. Многие правила поддерживают автоматическое исправление:
ruff check --fix .
Ruff сам уберёт неиспользуемые импорты, отсортирует их, заменит устаревший синтаксис (например, Union[int, str] на int | str для Python 3.10+) и исправит десятки других мелочей. Flake8 такого не умеет совсем — он только сообщает о проблемах.
Совместимость с Black
Форматтер Ruff (ruff format) разработан как drop-in замена Black. На практике расхождения минимальны, но они есть — в основном в edge-случаях с магическими trailing comma. Если ваша команда строго следует Black, стоит прогнать форматтер на реальном проекте и сравнить diff перед переходом.
Как подключить Ruff к реальному проекту за пять минут
Покажем на примере типичного Django-проекта с существующим Flake8 и Black.
Шаг 1. Установка
pip install ruff
# или через uv, что быстрее:
uv add --dev ruff
Шаг 2. Базовая конфигурация
Добавляем в pyproject.toml:
[tool.ruff]
line-length = 88
target-version = "py311"
exclude = [
"migrations",
".venv",
"__pycache__",
]
[tool.ruff.lint]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"B", # flake8-bugbear
"I", # isort
"C4", # flake8-comprehensions
"UP", # pyupgrade
]
ignore = [
"E501", # line too long — отдаём на откуп форматтеру
"B008", # do not perform function calls in default arguments (конфликт с FastAPI/Django)
]
[tool.ruff.format]
quote-style = "double"
Шаг 3. Первый запуск и автофикс
# Смотрим, что найдено
ruff check .
# Исправляем всё, что можно исправить автоматически
ruff check --fix .
# Форматируем код
ruff format .
После первого запуска на старом проекте вы, скорее всего, увидите несколько сотен предупреждений. Не пугайтесь — большинство из них исправятся автоматически. Остальные стоит просмотреть вручную.
Шаг 4. Интеграция в pre-commit
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.4
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
Шаг 5. Интеграция в GitHub Actions
# .github/workflows/lint.yml
name: Lint
on: [push, pull_request]
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/ruff-action@v1
with:
args: "check ."
- uses: astral-sh/ruff-action@v1
with:
args: "format --check ."
Официальный экшен astral-sh/ruff-action не требует установки Python и работает значительно быстрее, чем аналогичный шаг с Flake8 + Black.
Типичные подводные камни при переходе
Конфликт с существующим Black. Если вы используете Black параллельно с ruff format, они могут конфликтовать в edge-случаях. Решение простое: выберите одно или другое. Ruff format достаточно зрелый, чтобы его использовать как основной форматтер.
Правила, которых нет в Ruff. Некоторые специализированные плагины Flake8 ещё не реализованы в Ruff. Например, flake8-django или flake8-pytest-style покрыты частично. Проверяйте актуальный список поддерживаемых правил в документации — он обновляется с каждым релизом.
Слишком строгий select = ["ALL"]. Соблазн включить все 800+ правил велик, но это плохая идея для существующего проекта. Часть правил противоречит друг другу (например, правила про кавычки из разных наборов), часть — слишком педантична для реального кода. Начинайте с базового набора E/F/W/B/I и добавляйте группы осознанно.
Версионирование конфигурации. Ruff активно развивается, и между версиями могут появляться новые правила или меняться поведение существующих. Фиксируйте версию в requirements-dev.txt или pyproject.toml и обновляйте осознанно, просматривая changelog.
Когда Ruff не заменит pylint
Честный ответ: Ruff не является полноценной заменой pylint для глубокого статического анализа. Pylint умеет отслеживать типы через несколько файлов, анализировать цепочки наследования, предупреждать о потенциально некорректных операциях с типами без явных аннотаций. Если вам нужен именно такой уровень анализа — смотрите в сторону mypy или pyright в связке с Ruff, а не вместо них.
Ruff заменяет Flake8 + isort + pyupgrade + flake8-bugbear и берёт на себя форматирование вместо Black. Это уже очень много, и для большинства проектов этого достаточно.
Экосистема и поддержка
За Ruff стоит компания Astral — та же команда, что разрабатывает менеджер пакетов uv. Проект финансируется венчурными инвесторами и имеет полноценную команду разработчиков. Это важно для оценки долгосрочных рисков: инструмент не заброшен одним энтузиастом, а активно развивается.
Поддержка в редакторах также на высоком уровне: официальные расширения для VS Code и Neovim, интеграция в PyCharm через плагин, поддержка в Zed из коробки.
Для тех, кто только начинает разбираться с инструментарием Python и хочет понять Ruff не только на уровне «скопировал конфиг», но и осознать логику правил и настроек, есть бесплатный вводный курс «ruff – для начинающих!» — там разбирается именно практическая сторона: как читать вывод линтера, как выбирать правила под свой проект и как не сломать команду резким переходом.
Вывод
Ruff — это не хайп и не очередной инструмент ради инструмента. Это прагматичное решение реальной проблемы: разработчики Python тратили время на поддержку пяти разных конфигурационных файлов, ждали медленных проверок в CI и разбирались в конфликтах между isort и Black.
Переход на Ruff оправдан, если:
- вы устали от зоопарка инструментов и хотите один конфиг
- скорость CI для вас имеет значение
- вы хотите автофикс, а не только отчёты об ошибках
Оставить старый стек имеет смысл, если вы завязаны на специфические плагины Flake8, которые Ruff ещё не реализовал, или если вам критичен глубокий анализ типов — тогда pylint или mypy всё равно останутся в стеке.
Для большинства проектов пяти минут на подключение и один коммит с новым pyproject.toml — вполне разумная инвестиция. Если хочется разобраться в теме системнее, прежде чем менять конфигурацию в production, курс «ruff – для начинающих!» даст нужный контекст без лишней воды.
Комментарии
Пока нет комментариев