Какими инструментами для линтинга и форматирования Python-кода вы пользуетесь? Black, Isort, Flake? Их существует множество, каждый следует своей цели, хотя некоторые пересекаются по функциональности. Одни могут нравиться за автономность, другие — за возможности конфигурирования. И наверняка вы слышали о Ruff, который обещается заменить собой все. 

Привет, Хабр! Я Гена, Python-разработчик в Selectel. В этой статье я опишу свой опыт перевода проекта на Ruff: что понравилось, что — не очень, к чему готовиться и, если все же решитесь, то как это сделать. Добро пожаловать под кат.

Все, что описано в статье — исключительно мой опыт, основанный на практической работе. Статья будет полезна, если вы впервые сталкиваетесь с подобной задачей, либо хотите получить готовую инструкцию к действию.

Что обещает Ruff

Ruff — это чрезвычайно быстрый линтер для Python, написанный на Rust. Он обещает заменить собой многие инструменты, мгновенно проверять код и автоматически исправлять ошибки. К тому же он поддерживает современные стандарты, версии Python, интеграцию с основными IDE.

Вообще-то изначально я не сильно беспокоился о том, что в проектах, над которыми работал, было множество инструментов для форматирования кода. Я думал, что работают, ну и ладно, кто-то до меня же их установил. Но все же в некоторых небольших проектах скорость работы инструментов периферии меня не устраивала. А когда дело доходило до больших, она и вовсе начинала меня напрягать. И не только меня.

Как я перевозил готовый проект

Ну и как вы, наверное, понимаете, в один день я занялся прекрасной инициативой — принялся за исследование линтеров и форматтеров. По заявлениям из официальной документации, Ruff быстрее в 10-100 раз Flake8 и Black. Это внушало надежду.

Для ясности расскажу о первом проекте, который я переводил на Ruff. У нас в Selectel есть геймификация для сотрудников: весь год зарабатываешь баллы (селекоины) за различные достижения, а потом тратишь их на мерч, сувениры и разные прикольные штуки. Это называется Selectel Shop. Так вот, работает этот Selectel Shop на Python, и в нем была как раз уйма олдовых инструментов форматирования, от которых хотелось избавиться.

Как было «до»

Я быстро загорелся идеей перейти в этом проекте от легаси-инструментов к чему-то поновее. К тому же каждый раз при работе над фичей в тестовых окружениях приходилось ждать выполнение пайплайна CI/CD, в котором тоже выполнялись автоматические проверки кода линтерами.

Если рассказать подробнее, то в Selectel Shop использовались те самые Black и Flake8 со множеством плагинов, isort для сортировки импортов, pyupgrade, Autoflake и несколько pre-commit хуков (trailing-whitespace, end-of-file-fixer, debug-statements). По сути, все эти настройки с плагинами и аргументами можно было настроить в Ruff в виде правил проверки, выполняющих те же задачи.

Как стало «после»

Не нужно тешить себя иллюзиями, что Ruff — серебряная пуля, которая разгонит ваш проект от состояния престарелой черепахи до гоночного болида. Ruff не меняет проект кардинально, но находит множество мелочей, которые можно улучшить. А если проект достаточно большой и мелочей много, можно получить ощутимый профит от переезда.

Стоит сказать, что процесс перехода занял далеко не пять минут, как это бывает с коробочными решениями. Пришлось поэтапно определять функционал старых инструментов, находить правило в Ruff и тестировать. К этому делу нужно подходить с головой и закладывать время, чтобы со всем разобраться. Но результат безусловно того стоит.

Немного наглядности

Давайте я покажу несколько примеров того, что конкретно и как изменилось. Ниже на скрине один из сотни фиксов, которые Ruff предложил исправить. Если в новой версии Python улучшили синтаксис или типизацию, вы получаете рекомендацию по доработке кода:

Затем убираем легаси и улучшаем код:

В тоже время может быть предложена корректировка типизации, которая функционально сути не меняет:

Что вы тут видите? Казалось бы, какие-то мелкие изменения на уровне небольших настроек, мелкой оптимизации и прочего. Но в итоге когда я внедрил Ruff и убрал лишнее, мой конфиг pre-commit, выполняемый в процессе разработки, стал отрабатывать не пять секунд (по большей части, из-за плагинов Flake8), а за считанное мгновение. А если использовать Ruff в более объемных проектах, то можно сэкономить несколько дней жизни при ожидании работы линтера локально или в пайплайнах :)

Игровой сервер с криперами и порталом в Незер. Добывайте ресурсы, стройте объекты, исследуйте мир Selectel в Minecraft и получайте призы.

Исследовать →

Мои выводы

Краткое резюме: мне понравилось. Ruff очень информативно подсвечивает места возникновения ошибок и описывает, как их исправить. Можно вывести статистику и увидеть наглядно все группы замечаний и их количество.

Также радуют преимущества, которые получились в итоге:

  • единая конфигурация — все правила и настройки управляются в одном файле toml,

  • скорость — Ruff действительно работает быстрее аналогов,

  • автоматические исправления определенных правил.

Впрочем, если вы решитесь переходить на Ruff, то вам будет полезно знать о подводных камнях. Особенность этого инструмента в том, что его нужно обязательно конфигурировать. Не получится установить и забыть, как было, например, с flake8. Как выяснилось на практике, у коробочных решений были свои очевидные плюсы. Вы не тратите время на конфигурацию и исследование возможностей, а просто устанавливаете и используете. 

Ruff нужно изучить и подумать о том, какие проверки нужны вашему проекту. Теперь есть необходимость работать над списком правил проверки и отрабатывать те, которые ранее игнорировались. А еще обсуждать это все с коллегами и разбираться, что полезно, а что нет. Это, кстати, хорошая возможность внедрить новое полезное правило и получить респект от команды! А когда конфигурация будет готова, останется только наслаждаться скоростью исполнения проверок.

Хорошо это или нет, решать вам. Поскольку с Python нашей команде приходится работать много, мы решили масштабировать опыт с Ruff на другие проекты. Скорректировали список правил для проверки в зависимости от специфики сервиса и полезности применения, протестировали, обсудили, внедряли — это новый командный процесс, который появился у нас. А чтобы в будущем ничего не пропало, все нюансы фиксировали в Confluence. Кстати, угадайте, кто этим занимался ? 

Итак, если вы решите переехать на Ruff, то вот вам инструкция. Надеюсь, она окажется полезна.

Алгоритм действий по внедрению в проект

Ниже я пошагово опишу, что и как нужно делать, чтобы переехать на Ruff. 

Установка Ruff

Установить Ruff в виртуальном окружении можно согласно официальной документации:

# Установка с помощью UV
uv add --dev ruff
# Установка с помощью Poetry
poetry add ruff --group dev

Настройка файла конфигурации ruff.toml

  1. Добавляем максимальное число правил, от которых точно понимаем полезный эффект. Если не уверены в полезности, изучаем и обсуждаем с командой. Выбор правил может зависеть от проекта. Например, для проектов на FastApi следует добавить правило «FAST».

  2. Исправляем ошибки, если добавленные правила приводят к несущественным по времени исправлениям в коде. Если требуются серьезные исправления, добавляем правила в список игнорируемых (чем конкретнее код правила, тем лучше).

Пример одной из моих конфигураций ruff.toml:

# Поддержка версии Python
target-version = "py312"
 
# Основные настройки
line-length = 120 # Максимальная длина строки для кода
indent-width = 4 # Ширина отступа
 
# Каталоги, которые следует учитывать при разрешении импорта из first/third-party источников
src = ["src"]
 
# Настройки линтера
[lint]
# Нестабильные и находящиеся в предварительной версии правила
preview = true
# Правила, которые должен применять линтер
select = [
    "UP",   # pyupgrade
    "I",    # isort
    "E",    # flake8-errors
    "W",    # flake8-warnings
    "F",    # flake8-pyflakes
    "B",    # flake8-bugbear
    "C4",   # flake8-comprehensions
    "S",    # flake8-bandit
    "BLE",  # flake8-blind-except
    "T20",  # flake8-print
    "T10",  # flake8-debugger
    "PT",   # flake8-pytest-style
    "RUF",  # Ruff
    "DTZ",  # flake8-datetimez
    "ERA",  # eradicate
    "FURB", # refurb
]
# Игнорирование конкретных предупреждений линтера
ignore = [
    "C408",  # Ненужный вызов dict, list и т.д.
    "RUF001", # Игнорирование похожих символов с разными ASCII
    "RUF002", # Игнорирование похожих символов с разными ASCII
    "RUF003", # Игнорирование похожих символов с разными ASCII
    "S110",  # try-except-pass
]
# Исключение директорий и файлов из линтинга
exclude = [
    "src/tests/**",
]
# Ошибки, которые могут быть исправлены автоматически
fixable = [
    "I", # cортировка испортов
]
 
# Настройки форматтера
[format]
quote-style = "double" # 'single' для одинарных, "double" для двойных кавычек
indent-style = "space"
 
# Дополнительные настройки для плагинов
[lint.flake8-bandit]
check-typed-exception = true # Проверка типизированных исключений

Также конфигурацию можно поместить в pyproject.toml и не выделять в отдельный файл. Полный список правил можно посмотреть в официальной документации.

Обновление конфига .pre-commit

Чтобы Ruff проверял код автоматически перед отправкой в git, необходимо добавить его в файл .pre-commit-config.yaml

Нужно указать хуки для запуска линтера и форматтера. Для линтера рекомендую указать аргументы:

  • --fix (исправить правила, добавленные в раздел fixable конфигурации Ruff),

  • --show-fixes (для вывода исправлений в консоль).

Подробнее можно прочитать в документации в разделе интеграции с pre-commit.

- repo: https://github.com/astral-sh/ruff-pre-commit
  rev: v0.9.7
  hooks:
    # Run the linter.
    - id: ruff
      args: [ --fix, --show-fixes]
    # Run the formatter.
    - id: ruff-format

Удаление ненужных зависимостей

  • Нужно убрать из pyproject.toml неактуальные конфигурации. Пример: «tool.isort».

  • Удалить файлы конфигураций неактуальных инструментов. Пример: tox.ini, .isort.cfg, .pylintrc.

  • Удалить из файла .pre-commit-config.yaml конфигурации неактуальных инструментов. Пример: flake8, isort, autoflake, pyupgrade.

Локальное тестирование Ruff

Необходимо запустить скрипт pre-commit или отдельные команды Ruff для линтинга и форматирования кода.

# получаем отчет об ошибках, найденным в проекте
ruff check --statistics 
 
# получаем отчет о файлах, требующих форматирования в проекте
ruff format --check  
ruff format --check --diff # Также покажет какие именно изменения будут в виде diff
 
# Для реального исправление ошибок линтинга и форматирования, запускаем команды ниже. 
# Ruff автоматически внесет доступные исправления. Остальные рекомендации линтера нужно обработать самостоятельно.
 
# Исправление через pre-commit
pre-commit run --all-files
 
# Исправление через команды ruff
ruff check --fix
ruff format

Дополнительно

Для соблюдения оптимальной сборки Docker-образа рекомендуется добавить .ruff_cache в файл .dockerignore.

Какой опыт с Ruff был у вас? Возможно, у вас сложилось другое мнение об этом инструменте. Расскажите в комментариях! Оставлю небольшое напутствие: применяйте новые тулзы для улучшения своей работы и кайфуйте!

Комментарии (1)


  1. rSedoy
    17.07.2025 12:46

    Какой опыт с Ruff был у вас?

    выставляю настройку select = ["ALL"] а за ним в ignore дописываю, обычно со временем, меня не устраивающие