$ sudo teach IT
Модуль 7 · Продвинутое использование

Ruff против других инструментов

Сравнение с flake8, black, isort, pylint и mypy. Как мигрировать на Ruff и когда оставаться на старых инструментах.

📖 Теория 🕐 ~40 минут
🎯

Зачем сравнивать 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)
⚠️ Ruff не заменяет mypy и не пытается это делать — проверка типов это отдельная задача, требующая полноценного type checker'а. Ruff и mypy отлично работают вместе. Pylint также имеет уникальные возможности межмодульного анализа, которые Ruff пока не поддерживает.
📋

Детальное сравнение по категориям

Скорость работы

Инструмент 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 кеширует результаты между запусками.
⚡ Реальные бенчмарки на CPython (данные с официального репозитория Ruff): Ruff check — 0.5 секунды, flake8 — 30 секунд, pylint — 120 секунд. Разница в 60–240 раз.
📱

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 Статус
pycodestyleE, W✅ Полная
PyflakesF✅ Полная
pep8-namingN✅ Полная
flake8-bugbearB✅ Полная
flake8-banditS✅ Полная
flake8-docstringsD✅ Полная
flake8-simplifySIM✅ Полная
McCabeC90✅ Полная
flake8-annotationsANN✅ Полная
flake8-comprehensionsC4✅ Полная
flake8-import-orderI✅ Полная

Пошаговая миграция с flake8

Если в проекте есть .flake8 или секция [flake8] в setup.cfg, вот как перенести настройки в Ruff:

Было (.flake8)
[flake8]
max-line-length = 100
extend-ignore = E203, W503
per-file-ignores =
    __init__.py: F401
exclude = .git,__pycache__,migrations/,vendor/
max-complexity = 12
Стало (pyproject.toml)
[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/"]
💡 Обратите внимание: Ruff автоматически игнорирует .git и __pycache__. Их не нужно указывать в extend-exclude. Используйте extend-exclude вместо exclude — он добавляет к дефолтным правилам, а не заменяет их.

Дополнительные настройки flake8 и их эквиваленты

Настройка flake8 Эквивалент в Ruff
max-line-lengthline-length
extend-ignorelint.extend-ignore
per-file-ignoreslint.per-file-ignores
excludeexclude или extend-exclude
max-complexitylint.mccabe.max-complexity
selectlint.select
ignorelint.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

Было (black в pyproject.toml)
[tool.black]
line-length = 100
target-version = ["py311"]
skip-magic-trailing-comma = true
Стало (Ruff в pyproject.toml)
[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

Было (isort в pyproject.toml)
[tool.isort]
profile = "black"
line_length = 100
known_first_party = ["my_project"]
known_third_party = ["django", "flask"]
sections = ["STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
skip = ["migrations/"]
Стало (Ruff в pyproject.toml)
[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"]
⚠️ В отличие от isort, Ruff не поддерживает кастомные секции в сортировке импортов. Если вы используете нестандартные секции, возможно, придётся адаптировать конфигурацию. Однако большинство проектов используют стандартные секции, которые Ruff поддерживает полностью.
🏗️

Миграция с 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 не подойдёт.

💡 Хорошая новость: для 95% проектов Ruff — это всё, что нужно. Даже в крупных проектах Ruff покрывает все потребности в линтинге и форматировании. Проверку типов можно добавить отдельно (mypy), и это будет отличная комбинация.
💎

Рекомендуемая комбинация инструментов

Вот рекомендуемый набор инструментов для 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 обеспечивает качество через тестирование
  • Всё вместе работает за секунды, а не минуты
📈

Лучшие практики миграции

📋 Пошаговая стратегия миграции:

  1. Аудит текущих инструментов — составьте список всех линтеров и форматировщиков в проекте (flake8, black, isort, pylint, mypy, bandit, pydocstyle).
  2. Установить Ruff — добавьте ruff в зависимости проекта, создайте базовую конфигурацию.
  3. Сравнить результаты — запустите Ruff и старые инструменты параллельно, сравните вывод. Убедитесь, что Ruff находит те же ошибки.
  4. Перенести конфигурацию — перенесите ignore/select/per-file-ignores в Ruff. Используйте таблицы эквивалентов из этого урока.
  5. Запустить в CI — добавьте Ruff в CI/CD рядом со старыми инструментами.
  6. Удалить старые инструменты — когда команда привыкнет к Ruff, удалите flake8, black, isort и другие заменённые инструменты.
  7. Оставить только специфическое — 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-проектах.