Ruff против других инструментов
Сравнение с flake8, black, isort, pylint и mypy. Как мигрировать на Ruff и когда оставаться на старых инструментах.
Зачем сравнивать Ruff с другими инструментами
Экосистема Python-инструментов для анализа кода насчитывает десятки утилит: flake8, black, isort, pylint, mypy, pyupgrade, pydocstyle, bandit и многие другие. Каждый из них решает свою задачу, но у каждого есть недостатки — скорость, сложность настройки, конфликты между правилами, необходимость устанавливать множество плагинов.
Ruff появился как ответ на эту фрагментацию. Он объединяет большинство популярных линтеров и форматировщиков в один бинарник, работает в 10–100 раз быстрее и не требует внешних зависимостей. Но может ли он полностью заменить каждый из этих инструментов? Давайте разберёмся.
В этом уроке мы подробно сравним Ruff с каждым из основных инструментов, разберём, что Ruff заменяет полностью, что — частично, а что не заменяет вовсе. Вы узнаете, как мигрировать с existing инструментов на Ruff и как настроить его так, чтобы он максимально соответствовал вашему текущему рабочему процессу.
Важно понимать: цель Ruff — не уничтожить существующие инструменты, а предложить более быструю и удобную альтернативу для большинства повседневных задач. Для некоторых сценариев (глубокая проверка типов, сложный межмодульный анализ) специализированные инструменты остаются незаменимыми — и Ruff прекрасно с ними сочетается.
Полная таблица сравнения
В таблице ниже приведено сравнение Ruff с основными инструментами Python-экосистемы по ключевым параметрам: функциональность, скорость, сложность настройки и степень замены в Ruff.
| Инструмент | Что делает | Скорость | Кол-во правил | Замена в Ruff |
|---|---|---|---|---|
flake8 |
Линтинг PEP 8 + Pyflakes + плагины | Медленный (30-60с на CPython) | ~100 (ядро) + плагины | ✅ Полная |
black |
Форматирование кода | Средний (10с на CPython) | — | ✅ Полная |
isort |
Сортировка импортов | Средний (5с на CPython) | ~10 | ✅ Полная (правила I) |
pylint |
Глубокий анализ кода | Очень медленный (120с на CPython) | ~600 | ⚠️ Частичная (~90%) |
mypy |
Статическая проверка типов | Средний (зависит от проекта) | ~100+ | ❌ Нет |
pyupgrade |
Обновление синтаксиса до современного Python | Быстрый | ~50 | ✅ Полная (правила UP) |
bandit |
Проверка безопасности | Медленный | ~200+ | ✅ Полная (правила S) |
pydocstyle |
Проверка docstring | Средний | ~70 | ✅ Полная (правила D) |
pep8-naming |
Проверка именования | Быстрый | ~30 | ✅ Полная (правила N) |
flake8-bugbear |
Поиск багов | Быстрый | ~80 | ✅ Полная (правила B) |
Детальное сравнение по категориям
Скорость работы
| Инструмент | CPython (1.5 млн строк) | Типичный проект (10К строк) | Относительно Ruff |
|---|---|---|---|
| Ruff check | ~0.5 с | ~0.02 с | 1x |
| Ruff format | ~0.3 с | ~0.01 с | 1x |
| flake8 | ~30 с | ~1-2 с | ~60x медленнее |
| black | ~10 с | ~0.5 с | ~20x медленнее |
| isort | ~5 с | ~0.3 с | ~10x медленнее |
| pylint | ~120 с | ~5-10 с | ~240x медленнее |
| mypy | ~30-60 с | ~2-5 с | — (другая задача) |
Разница в скорости обусловлена несколькими факторами:
- Язык реализации: Ruff на Rust, остальные — на Python. Rust-бинарник запускается напрямую, без интерпретатора.
- Параллелизм: Ruff обрабатывает файлы в несколько потоков, используя все ядра CPU.
- Объединённый AST: Ruff строит одно AST и прогоняет все правила по нему, вместо многократного парсинга.
- Кеширование: Ruff кеширует результаты между запусками.
Feature-матрица: что умеет Ruff vs другие
| Фича | Ruff | flake8 | black | isort | pylint | mypy |
|---|---|---|---|---|---|---|
| PEP 8 стиль (E/W) | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
| Pyflakes (F) | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
| Сортировка импортов (I) | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
| Форматирование | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| Упрощение кода (SIM) | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ |
| Проверка типов | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
| Межмодульный анализ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
| Автоисправление (--fix) | ✅ | ❌ | ✅ | ✅ | ⚠️ | ❌ |
| pyproject.toml конфиг | ✅ | ⚠️ | ✅ | ✅ | ⚠️ | ✅ |
| JSON/GitHub вывод | ✅ | ❌ | ❌ | ❌ | ⚠️ | ❌ |
Как видно из таблицы, Ruff покрывает функциональность flake8, black, isort, pyupgrade, bandit, pydocstyle и многих других плагинов в одном инструменте. Это означает, что вам не нужно настраивать и запускать 5–10 разных инструментов — один Ruff делает всё.
Миграция с flake8
Flake8 — один из самых популярных линтеров в Python-сообществе. Он объединяет pycodestyle (PEP 8), Pyflakes (логические ошибки) и McCabe (сложность кода). Ruff полностью заменяет flake8, включая большинство популярных плагинов.
Плагины flake8, которые заменяет Ruff
| Плагин flake8 | Префикс Ruff | Статус |
|---|---|---|
| pycodestyle | E, W | ✅ Полная |
| Pyflakes | F | ✅ Полная |
| pep8-naming | N | ✅ Полная |
| flake8-bugbear | B | ✅ Полная |
| flake8-bandit | S | ✅ Полная |
| flake8-docstrings | D | ✅ Полная |
| flake8-simplify | SIM | ✅ Полная |
| McCabe | C90 | ✅ Полная |
| flake8-annotations | ANN | ✅ Полная |
| flake8-comprehensions | C4 | ✅ Полная |
| flake8-import-order | I | ✅ Полная |
Пошаговая миграция с flake8
Если в проекте есть .flake8 или секция [flake8] в setup.cfg, вот как перенести настройки в Ruff:
[flake8]
max-line-length = 100
extend-ignore = E203, W503
per-file-ignores =
__init__.py: F401
exclude = .git,__pycache__,migrations/,vendor/
max-complexity = 12
[tool.ruff]
line-length = 100
[tool.ruff.lint]
extend-ignore = ["E203", "W503"]
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]
[tool.ruff.lint.mccabe]
max-complexity = 12
[tool.ruff]
extend-exclude = ["migrations/", "vendor/"]
.git и __pycache__. Их не нужно указывать в extend-exclude. Используйте extend-exclude вместо exclude — он добавляет к дефолтным правилам, а не заменяет их.
Дополнительные настройки flake8 и их эквиваленты
| Настройка flake8 | Эквивалент в Ruff |
|---|---|
max-line-length | line-length |
extend-ignore | lint.extend-ignore |
per-file-ignores | lint.per-file-ignores |
exclude | exclude или extend-exclude |
max-complexity | lint.mccabe.max-complexity |
select | lint.select |
ignore | lint.ignore |
Миграция с black
ruff format — это форматировщик, который является «совместимой заменой» black. Это значит, что на любом проекте, отформатированном black, ruff format даст тот же результат в 99% случаев. Основные различия минимальны и касаются редких edge case.
Сходства ruff format и black
- Тот же стиль — ruff format следует тем же принципам, что и black: неизменяемый стиль, 88 символов по умолчанию, «бескомпромиссное» форматирование
- Совместимость с PEP 8 — оба инструмента следуют PEP 8 с некоторыми отклонениями (E203, W503)
- Magic trailing comma — оба используют запятые для управления вертикальным форматированием
- Стабильность — ruff format, как и black, гарантирует детерминированный вывод
Различия ruff format и black
| Аспект | ruff format | black |
|---|---|---|
| Скорость | В 10-20x быстрее | Медленнее |
| Строки с `fmt: skip` | Поддерживает | Поддерживает |
| Вложенные `with` | Форматирует компактнее | Более вертикальное |
| Стиль кавычек | Настраиваемый (single/double) | Только двойные |
| `isort` интеграция | Встроенная (правила I) | Через отдельный isort |
| Вывод diff | `--diff` | `--diff` |
| Проверка без изменений | `--check` | `--check` |
Перенос конфигурации black
[tool.black]
line-length = 100
target-version = ["py311"]
skip-magic-trailing-comma = true
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.format]
exclude = []
quote-style = "preserve" # или "double", "single"
ruff format понимает # fmt: on/off и # fmt: skip так же, как black. Миграция с black на ruff format в большинстве случаев — это просто замена команды в CI/CD.
Миграция с isort
Ruff полностью заменяет isort через правила категории I (isort). Все основные функции isort поддерживаются: группировка импортов (стандартные библиотеки, third-party, локальные), сортировка по алфавиту, разделение на секции, known-first-party, known-third-party и другие.
Перенос конфигурации isort
[tool.isort]
profile = "black"
line_length = 100
known_first_party = ["my_project"]
known_third_party = ["django", "flask"]
sections = ["STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
skip = ["migrations/"]
[tool.ruff.lint]
select = ["I"]
[tool.ruff.lint.isort]
known-first-party = ["my_project"]
known-third-party = ["django", "flask"]
required-imports = ["from __future__ import annotations"]
extra-standard-library = ["tomllib"]
Миграция с pylint
Pylint — самый старый и самый полный линтер для Python. Он умеет не только проверять стиль, но и проводить глубокий анализ кода. Ruff покрывает ~90% правил pylint, но некоторые возможности pylint остаются уникальными.
Что pylint умеет, а Ruff — нет
Ruff покрывает большинство правил pylint, но не все. Вот что pylint умеет делать лучше или что Ruff пока не поддерживает:
- Межмодульный анализ — pylint понимает связи между файлами и может, например, найти неиспользуемые публичные функции/классы в модуле (
C0114,C0115,C0116). Ruff работает в рамках одного файла. - Анализ потока управления — pylint находит недостижимый код, всегда истинные/ложные условия, зацикливания. Ruff имеет базовые проверки, но pylint глубже.
- Глубокая проверка классов и наследования — pylint проверяет корректность MRO (Method Resolution Order), сигнатуры переопределённых методов, абстрактные классы.
- Проверка строк документации — pylint проверяет не только наличие docstring, но и соответствие параметров/возвращаемых значений.
- Кастомные плагины — pylint поддерживает написание собственных плагинов на Python. Ruff не поддерживает кастомные плагины (но поддерживает более 900 встроенных правил).
- Логирование — pylint находит использование
%форматирования вместо f-строк в логах, проверяет корректность вызова логгера.
Сравнение правил pylint и Ruff
| Категория pylint | Покрытие Ruff | Примечание |
|---|---|---|
| Конвенции (C) | ~80% | Нет проверки docstring конвенций |
| Рефакторинг (R) | ~90% | Ruff покрывает через SIM, PL |
| Предупреждения (W) | ~95% | Почти полное покрытие |
| Ошибки (E) | ~95% | Почти полное покрытие |
| Фатальные (F) | 100% | Синтаксические ошибки |
Для большинства проектов Ruff более чем достаточен. Pylint оправдан в крупных проектах с высокими требованиями к качеству (например, в авиации, медицинском ПО, финансовом секторе). Если вы используете pylint в основном для проверки стиля — смело переходите на Ruff. Если же вам нужен глубокий анализ — оставьте pylint, но используйте Ruff для быстрой проверки первого уровня.
Другие инструменты и их замена
pyupgrade → Ruff (правила UP)
Pyupgrade автоматически обновляет синтаксис Python до современной версии. Ruff полностью заменяет его через правила категории UP:
# Было (старый синтаксис)
from typing import List, Optional
x: List[str] = []
y: Optional[str] = None
# После UP006, UP007 (ruff check --fix)
x: list[str] = []
y: str | None = None
# Было: with open("file") as f: content = f.read()
# После UP012 (PEP 608)
# (Ruff автоматически заменит на pathlib методы)
bandit → Ruff (правила S)
Bandit проверяет безопасность кода: SQL-инъекции, использование eval(), доступ к файловой системе и т.д. Ruff заменяет его через правила S:
# S101 — не используйте assert в production
assert user.is_admin # S101
# S701 — уязвимость XSS в jinja2
from jinja2 import Template
Template("{{ user_input }}").render() # S701
# S324 — устаревший модуль
import md5 # S324
pydocstyle → Ruff (правила D)
Pydocstyle проверяет наличие и формат docstring. Ruff поддерживает все популярные конвенции: Google, NumPy, PEP 257.
[tool.ruff.lint]
select = ["D"] # все docstring правила
[tool.ruff.lint.pydocstyle]
convention = "google" # или "numpy", "pep257"
autoflake → Ruff (--fix)
Autoflake удаляет неиспользуемые импорты и переменные. Ruff делает это через ruff check --fix с правилами F401, F841.
Что Ruff не заменяет и не должен заменять
Важно понимать границы применимости Ruff. Есть задачи, которые Ruff решать не должен и не будет:
❌ Проверка типов (mypy, pyright, pyre)
Ruff не является type checker'ом. Он не проверяет корректность аннотаций типов, не выводит типы, не проверяет соответствие типов в рантайме. Для этого нужен mypy или pyright. Но Ruff отлично с ними сочетается: mypy проверяет типы, Ruff — стиль и форматирование.
❌ Глубокий межмодульный анализ (pylint)
Pylint умеет анализировать связи между модулями: неиспользуемые публичные функции, неправильные вызовы, циклические зависимости. Ruff работает в рамках одного файла. Если вам нужен такой анализ — используйте pylint.
❌ Кастомные плагины
Ruff не поддерживает пользовательские плагины. Все правила встроены в бинарник. Если у вас есть специфические требования, которые не покрываются 900+ встроенными правилами — вам придётся либо использовать другой инструмент, либо запросить добавление правила в Ruff.
❌ Форматирование, отличное от black-стиля
ruff format следует стилю black. Если вам нужен другой стиль форматирования (например, как у yapf с кастомными настройками), ruff format не подойдёт.
Рекомендуемая комбинация инструментов
Вот рекомендуемый набор инструментов для Python-проекта любого размера:
| Задача | Инструмент | Почему |
|---|---|---|
| Линтинг (стиль, баги, безопасность) | Ruff check | Быстрее всех, покрывает 50+ плагинов |
| Форматирование | Ruff format | Совместим с black, быстрее в 10-20x |
| Сортировка импортов | Ruff check --select I | Встроено в Ruff, не нужен isort |
| Проверка типов | mypy | Стандарт де-факто для type checking |
| Тестирование | pytest | Стандарт для тестов |
Эта комбинация закрывает все потребности современного Python-проекта:
- Ruff check + Ruff format = 2 команды вместо 5-7
- Mypy добавляет безопасность типов
- Pytest обеспечивает качество через тестирование
- Всё вместе работает за секунды, а не минуты
Лучшие практики миграции
📋 Пошаговая стратегия миграции:
- Аудит текущих инструментов — составьте список всех линтеров и форматировщиков в проекте (flake8, black, isort, pylint, mypy, bandit, pydocstyle).
- Установить Ruff — добавьте ruff в зависимости проекта, создайте базовую конфигурацию.
- Сравнить результаты — запустите Ruff и старые инструменты параллельно, сравните вывод. Убедитесь, что Ruff находит те же ошибки.
- Перенести конфигурацию — перенесите ignore/select/per-file-ignores в Ruff. Используйте таблицы эквивалентов из этого урока.
- Запустить в CI — добавьте Ruff в CI/CD рядом со старыми инструментами.
- Удалить старые инструменты — когда команда привыкнет к Ruff, удалите flake8, black, isort и другие заменённые инструменты.
- Оставить только специфическое — mypy оставьте, pylint — если нужен глубокий анализ.
📌 Что важно запомнить:
- Ruff полностью заменяет flake8, black, isort, pyupgrade, autoflake, bandit, pydocstyle и 50+ плагинов
- Ruff частично заменяет pylint (~90% правил), но не заменяет межмодульный анализ
- Ruff не заменяет mypy — проверка типов это отдельная задача
- Ruff в 10–240 раз быстрее аналогов (зависит от инструмента)
- Для миграции используйте таблицы эквивалентов из этого урока
- Лучшая комбинация: Ruff + mypy + pytest
- Миграция должна быть постепенной — не удаляйте старые инструменты, пока не убедитесь, что Ruff работает корректно
Что дальше
Теперь вы знаете, как Ruff соотносится с другими инструментами и как мигрировать на него. В следующем уроке мы рассмотрим настройку Ruff для больших проектов: exclude, target-version, монорепо, и стратегии постепенного внедрения в legacy-проектах.