$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогRuff + форматирование: как внедрить единый стиль в проект без “войны” и сломанных CI

Ruff + форматирование: как внедрить единый стиль в проект без “войны” и сломанных CI

$ sudo teach IT
·9 августа 2026 г.·10 мин·33
Ruff + форматирование: как внедрить единый стиль в проект без “войны” и сломанных CI

Покажем, как настроить Ruff так, чтобы линтинг и автозамены работали согласованно с форматированием, и как мягко мигрировать существующий код. Будут примеры конфигурации, стратегии исключений и как не увеличить время проверок в CI.

Содержание
Что именно ломается в реальных проектахДве разные сущности: lint и formatНесогласованные конфиги в локальной среде и CIЛинтится больше файлов, чем нужноБазовая концепция: один источник истины и одна точка примененияНастройка Ruff: конфигурация под единый стильМинимальный рабочий pyproject.tomlЛокальная команда “приведи к стандарту”Что важно для согласованностиФорматирование vs линтинг: как избежать цикла “туда-сюда”Почему вообще возникает циклСтратегия “одного форматтера”Миграция существующего кода без “войны”Шаг 1: начать с “текущего состояния” (baseline)Шаг 2: использовать “мягкие” ограничения на миграциюШаг 3: поэтапное ужесточениеКак не увеличить время проверок в CI1) Ограничивайте область проверки2) Используйте кэш (по умолчанию Ruff его использует)3) Не запускайте авто-фиксы в CI (или делайте это только в режиме “быстро починили”)4) Сведите команды в один job и не дублируйте работуРецепт CI: проверка без “поломок” и с предсказуемым поведениемПочему это “мягче”, чем кажетсяСтратегии исключений, которые не убивают дисциплинуПравило №1: исключайте на уровне директории/паттерна, а не на уровне каждой строкиПравило №2: помечайте исключения как временныеПравило №3: не отключайте правила целиком без причиныЧастые подводные камни при внедрении Ruff форматированияПодводный камень: расхождение версий инструментаПодводный камень: разные Python-версииПодводный камень: форматирование без проверки --checkПодводный камень: слишком “широкая” стартовая конфигурацияПрактический план внедрения за 3–5 итерацийИтерация 1: диагностика и baselineИтерация 2: привести код к форматуИтерация 3: авто-фиксы lintИтерация 4: закрепить дисциплину в CIИтерация 5: ужесточение по правиламКогда стоит задуматься о обучающем материалеВыводы

В большинстве Python-проектов стиль начинает жить собственной жизнью. Сначала это «пара замечаний» от линтера, затем появляются авто-фиксы, потом кто-то вручную начинает форматировать код в IDE, и постепенно возникает хроническая проблема: один инструмент «выигрывает» у другого. В итоге в PR копятся циклы вида:

  • Ruff сообщает о нарушениях
  • CI заваливается
  • разработчик запускает авто-фикс
  • но форматирование меняется снова (или наоборот)
  • PR превращается в битву за формат, а не за смысл

Эта статья — практический разбор того, как настроить Ruff так, чтобы линтинг, автозамены и форматирование работали согласованно, и как мигрировать существующий код без «войны» и без роста времени проверок в CI.


Что именно ломается в реальных проектах

Перед настройкой полезно понять, где обычно появляется конфликт.

Две разные сущности: lint и format

Ruff в первую очередь — статический анализ (lint). Но в современных версиях он также умеет форматировать (через правила форматтера/код-стайла) и делать авто-фиксы. Проблема в том, что форматирование может:

  • конкурировать с Black/isort/yapf и т.п.
  • менять структуру строк так, что повторно задеваются правила lint
  • применяться не в том же режиме, что ожидает CI

Несогласованные конфиги в локальной среде и CI

Чаще всего CI заваливается не потому, что «Ruff сломан», а потому что:

  • локально запускался ruff check --fix, а в CI — ruff format --check (или наоборот)
  • конфиг берётся из другого файла (pyproject.toml vs ruff.toml)
  • игнор-листы (exclude, extend-exclude, per-file-ignores) отличаются
  • команды выполняются с разными наборами правил

Линтится больше файлов, чем нужно

Если миграция делается «в лоб», то можно легко разогнать CI время:

  • Ruff запускается на всём репозитории, включая docs, venv, генерируемые артефакты
  • включается слишком широкий набор правил форматирования
  • авто-фиксы выполняются в CI, а затем ещё и форматирование прогоняется отдельно

Базовая концепция: один источник истины и одна точка применения

Если свести подход к формуле, то она выглядит так:

  1. Один конфиг (обычно pyproject.toml) — для всех запусков Ruff.
  2. Одна цепочка действий: сначала форматирование (если вы его используете), затем линт+фиксы, и в CI только проверки (или с очень аккуратными исключениями).
  3. Единый набор правил и одинаковые флаги локально/в CI.
  4. Мягкая миграция: постепенно расширять покрытие и ужесточать правила.

Дальше — конкретика.


Настройка Ruff: конфигурация под единый стиль

Минимальный рабочий pyproject.toml

Ниже — каркас, который хорошо подходит под цель: единый стиль, согласованные проверки и минимальная «война».

code
[tool.ruff]
target-version = "py311"
line-length = 88
src = ["src", "tests"]

# Важно: исключения делаем один раз и используем везде.
exclude = [
  ".git",
  ".venv",
  "venv",
  "build",
  "dist",
  ".mypy_cache",
  ".ruff_cache",
  ".pytest_cache",
]

# Для сокращения шума в миграции можно начать с выборочного выбора правил.
# Но ниже пример ориентирован на достаточно полный набор.
lint.select = [
  "E",
  "F",
  "I",  # isort-like (через ruff)
  "UP",
  "B",
  "SIM",
]

# Игнор-листы лучше держать узкими и объяснимыми.
lint.ignore = [
  # пример: не ругать конкретный кейс, если он осознанный
  "B008",
]

[tool.ruff.format]
# Если проект использует Ruff format, выбирайте параметры один раз.
# line-length будет согласован с [tool.ruff] line-length.
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false

# Автофиксы: Ruff умеет их делать, но иногда нужно контролировать поведение.
# Включение/выключение отдельных опций — по месту и под проект.

Примечание: конкретные поля форматтера зависят от версии Ruff. Идея в том, что формат и lint должны ссылаться на один и тот же pyproject.toml и одни и те же параметры (в частности line-length).

Локальная команда “приведи к стандарту”

Обычно удобно завести одну команду для разработчиков — чтобы она делала то же самое, что ожидает CI.

Хорошая практика: разделить «форматирование» и «фиксы» на один скрипт.

code
ruff format .
ruff check --fix .

Если у вас большой репозиторий, стоит добавить к ruff check ограничения по директориям (например, ruff check src tests), чтобы не тратить время на то, что не влияет на продукт.

Что важно для согласованности

  1. В CI не запускаем форматирование с “не теми опциями”. Форматтер должен быть тем же ruff format и тем же pyproject.toml.
  2. Авто-фиксы не должны запускаться после форматирования с другой логикой. Обычно порядок такой:
    • ruff format (привести к стилю)
    • ruff check --fix (починить то, что может быть исправлено линтером)
  3. Если в проекте уже используется Black/isort: сначала определитесь, кто «владелец» форматирования. Иначе вы получите перетягивание каната. Ruff удобно сделать основным владельцем, а старые инструменты постепенно отключать.

Форматирование vs линтинг: как избежать цикла “туда-сюда”

Почему вообще возникает цикл

Если вы включили:

  • Ruff форматирует (ruff format)
  • Ruff линтит (ruff check) и делает фиксы
  • при этом другой инструмент (Black/isort/IDE) также форматирует

— то изменения могут происходить в несовместимых представлениях. Пример: ruff format меняет переносы/кавычки/выравнивания, а следом ruff check --fix меняет импорты или отдельные конструкции, которые форматтер снова перекомпонует, либо наоборот.

Стратегия “одного форматтера”

Самый практичный подход:

  • либо только ruff format (если проект готов принять стиль Ruff),
  • либо оставить Black, но тогда Ruff format выключить (и тогда конфликтов будет меньше),
  • либо удерживать форматирование в одном инструменте до завершения миграции.

В статье мы рассматриваем вариант, когда Ruff — владелец. Тогда в проекте следует:

  • перестать использовать Black как обязательный шаг CI
  • отключить isort (если Ruff уже покрывает сортировку импортов и фиксы)

Миграция существующего кода без “войны”

Проблема миграции обычно не в конфигурации как таковой, а в объёме изменений.

Если прямо сейчас включить максимально строгие правила, то вы получите пачку правок по всему репозиторию. Это плохо для:

  • ревью (сложно отличить шум от изменений)
  • стабильности CI (много единовременных правок)
  • предсказуемости для разработчиков

Шаг 1: начать с “текущего состояния” (baseline)

Один из самых спокойных сценариев — сначала собрать картину: какие правила реально нарушены. Для этого запускают:

code
ruff check .
ruff format --check .

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

code
ruff format .

Затем делаете авто-фиксы линтера:

code
ruff check --fix .

После этого CI должен стать более стабильным.

Шаг 2: использовать “мягкие” ограничения на миграцию

Если код большой и часть нарушений вы не хотите исправлять сразу, используйте точечные исключения.

Пер-строка/пер-файл через per-file-ignores

Например, часто проблемы возникают в тестах или в legacy-коде:

code
[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = [
  "S101",  # assert в тестах (пример)
  "F401",  # неиспользуемые импорты в тестах иногда осознанны
]
"src/legacy/**/*.py" = [
  "E501",  # длина строк: пока не мигрировали
]

Важно: такие исключения должны быть временно. В противном случае «legacy-исключение» превращается в постоянную яму.

Расширенные исключения extend-exclude

Если есть директории, которые не должны проверяться вообще:

code
[tool.ruff]
extend-exclude = ["migrations", "scripts/generated"]

Это экономит время и снижает шум.

Шаг 3: поэтапное ужесточение

Вместо того чтобы сразу включить весь набор правил, можно:

  • сначала покрыть только ошибки (E, F)
  • затем добавить стиль (I, UP, SIM, и т.п.)
  • потом — более спорные правила

Технически это делается через lint.select и/или постепенно расширяемые lint.ignore/per-file-ignores.


Как не увеличить время проверок в CI

Время CI — часто первый “больной вопрос” после внедрения линтера.

1) Ограничивайте область проверки

Самая простая оптимизация: не проверять всё подряд.

В конфиге уже был src = ["src", "tests"]. Это помогает, но в командах CI лучше явно указать директории:

code
ruff check src tests
ruff format --check src tests

2) Используйте кэш (по умолчанию Ruff его использует)

У Ruff есть --cache-dir (или стандартные механизмы кэша в .ruff_cache). В CI кэш стоит сохранять, если ваша система поддерживает артефакты/кэширование.

Пример подхода (на уровне концепции):

  • кэшировать .ruff_cache
  • и не очищать кэш при каждом прогоне

3) Не запускайте авто-фиксы в CI (или делайте это только в режиме “быстро починили”)

Рекомендация для стабильного процесса:

  • CI должен проверять, а не “редактировать” код.
  • авто-фиксы — на стороне разработчиков (локально) и/или в отдельном job/скрипте, но не как часть обязательного шага на каждый PR.

Тогда ваш CI не будет:

  • тратить время на запись файлов
  • менять рабочую копию
  • скрывать источники расхождений

4) Сведите команды в один job и не дублируйте работу

Не нужно запускать несколько раз ruff check . и затем снова ruff check. Лучше сформировать один набор команд:

  • ruff format --check ...
  • ruff check ...

Рецепт CI: проверка без “поломок” и с предсказуемым поведением

Ниже пример общей структуры для GitHub Actions. Подберите под вашу CI-платформу.

code
name: lint

on:
  pull_request:
  push:
    branches: [ "main" ]

jobs:
  ruff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install ruff

      # Желательно кэшировать .ruff_cache, если CI позволяет.
      # Здесь оставлено концептуально.

      - name: Check formatting
        run: ruff format --check src tests

      - name: Check lint
        run: ruff check src tests

Почему это “мягче”, чем кажется

  • ruff format --check гарантирует, что форматтер в CI соответствует локальному.
  • ruff check проверяет lint с учётом конфигурации.
  • Никаких --fix в CI — значит, не будет “скрытых правок”.

Стратегии исключений, которые не убивают дисциплину

Исключения — нормальная часть жизни, особенно при миграции. Но они должны быть “управляемыми”.

Правило №1: исключайте на уровне директории/паттерна, а не на уровне каждой строки

Если вы начинаете добавлять # noqa: ... в сотнях мест — проект постепенно теряет смысл линтинга как системы, а не как генератора шума.

Вместо этого используйте:

  • per-file-ignores
  • extend-exclude
  • разделение на “legacy/новая кодовая база”

Правило №2: помечайте исключения как временные

Ruff не заставляет вас документировать причины, но вы можете делать это на уровне конфига (или во внутренней документации).

Например:

code
[tool.ruff.lint.per-file-ignores]
"src/legacy/**/*.py" = [
  # TODO: убрать после миграции стиля и рефакторинга конструкций
  "E501",
]

Это снижает риск, что исключение “навсегда”.

Правило №3: не отключайте правила целиком без причины

lint.ignore = ["E"] на старте миграции выглядит соблазнительно, но потом будет трудно возвращаться в нормальный режим. Лучше:

  • временно ослабить набор select
  • точечно исключить больные места

Частые подводные камни при внедрении Ruff форматирования

Подводный камень: расхождение версий инструмента

Ruff развивается быстро. Форматтер может менять детали форматирования между версиями. Поэтому:

  • фиксируйте версию Ruff в зависимостях (например, в requirements-dev.txt или через lock-файл)
  • используйте один и тот же инструмент и в локальной среде, и в CI

Подводный камень: разные Python-версии

target-version влияет на то, какие конструкции считаются “современными”. Убедитесь, что он соответствует реальному рантайму проекта.

Подводный камень: форматирование без проверки --check

Иногда команда форматирования запускается без проверки. В результате:

  • локально всё выглядит нормально
  • в CI формат не совпадает, потому что кто-то не прогнал ruff format

Решение: CI обязательно должен включать ruff format --check.

Подводный камень: слишком “широкая” стартовая конфигурация

Начинать лучше с базовых категорий (E, F) и постепенно добавлять остальное. Полный набор сразу может привести к огромной первой волне коммитов.


Практический план внедрения за 3–5 итераций

Ниже сценарий, который на практике обычно работает для команд.

Итерация 1: диагностика и baseline

  1. Зафиксировать текущие нарушения:
    • ruff check .
    • ruff format --check .
  2. Создать PR только с конфигурацией (и возможно — с массовыми фиксациями форматтера, если это допустимо).

Итерация 2: привести код к формату

  1. ruff format .
  2. git commit отдельным коммитом (чтобы ревью было легче).

Итерация 3: авто-фиксы lint

  1. ruff check --fix .
  2. Коммит отдельно.

Итерация 4: закрепить дисциплину в CI

  1. Добавить ruff format --check и ruff check в CI.
  2. Убедиться, что команды проверяют только нужные директории.

Итерация 5: ужесточение по правилам

  1. Постепенно расширить lint.select.
  2. Убрать часть per-file-ignores там, где код уже мигрировал.
  3. Следить за временем CI и областью проверки.

Когда стоит задуматься о обучающем материале

Если в вашей команде пока меньше практики по Ruff (или конфигурации выглядят как набор “магических” опций), полезно пройти структурированный материал. Например, курс **«ruff – для начинающих!»** может помочь быстрее разобраться в том, как устроены select, ignore, фиксы и форматирование — без необходимости методом проб и ошибок на реальном репозитории.


Выводы

Внедрение Ruff с форматированием — это не “включить одну кнопку”, а настройка режима взаимодействия инструментов: линтинга, авто-фиксов и форматтера. Чтобы не получить “войну” и сломанные CI:

  • сделайте один источник истины в конфигурации (pyproject.toml)
  • обеспечьте согласованную цепочку: ruff format → ruff check (с фиксацией только локально/отдельно)
  • мигрируйте поэтапно, используя точечные исключения (per-file-ignores, extend-exclude)
  • держите CI в режиме проверок, избегая --fix на каждом PR
  • оптимизируйте время: проверяйте только нужные директории и используйте кэш

Так вы получите единый стиль, предсказуемые PR и линтер, который помогает разработке, а не отвлекает от неё.

Если хотите, могу предложить пример готового pyproject.toml под ваш стек (Poetry/uv/pip, структура папок, версии Python, есть ли Black/isort и какие правила сейчас конфликтуют) — но для этого нужно знать текущие команды CI и конфигурации форматирования.

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

ruff – для начинающих!

Курс по теме

ruff – для начинающих!

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

Бесплатно
Открыть курс

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу

Приложения для iPhone и Apple Watch на SwiftUI

Разработка приложений для iPhone и Apple Watch на SwiftUI: навигация, SwiftData, виджеты, часы, выпуск. Нужен Mac с Xcode 27, сами устройства не нужны.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу

Приложения для macOS на SwiftUI

Разработка приложений для Mac на SwiftUI: окна и меню, Liquid Glass, SwiftData, сеть, выпуск. Нужен Mac с macOS 27 и Xcode 27.

Перейти к курсу

Другие статьи

ruff на максималках: правила, исключения и единый стиль команды
ruff

ruff на максималках: правила, исключения и единый стиль команды

Как договориться о правилах, настроить конфиг под реальный код-стайл команды и не спорить о форматировании и линтинге бесконечно.

21 июля 2026 г.
450
Ruff против Flake8 и Black: почему стоит перейти на один инструмент
ruff

Ruff против Flake8 и Black: почему стоит перейти на один инструмент

Сравниваем популярные линтеры и форматтеры Python по скорости, гибкости настройки и удобству интеграции в CI. Показываем, как за пять минут подключить Ruff к реальному проекту.

17 июля 2026 г.
430
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1061
Какой первый проект выбрать новичку, чтобы не бросить обучение
первый

Какой первый проект выбрать новичку, чтобы не бросить обучение

Подберём 5–7 идей под уровень “с нуля”, объясним, что делать по шагам и как довести проект до результата без перегруза. В конце — как оформить мини-портфолио и что показать, даже если проект маленький.

23 сентября 2026 г.
150
FastAPI и фоновые задачи: когда лучше использовать BackgroundTasks, а когда очередь
fastapi

FastAPI и фоновые задачи: когда лучше использовать BackgroundTasks, а когда очередь

Поймём различия между синхронной обработкой, BackgroundTasks и внешними очередями. Разберём idempotency, ретраи и мониторинг фоновых процессов.

22 июля 2026 г.
760
Как выучить программирование самому и бесплатно: с чего начать и в каком порядке
выучить

Как выучить программирование самому и бесплатно: с чего начать и в каком порядке

Разберём понятный маршрут для новичка: как выбрать первый язык, что учить в первую очередь, какие мини-проекты делать и как не забросить занятия. Дам бесплатные ориентиры и план, чтобы знания складывались в портфолио.

21 сентября 2026 г.
250

Комментарии

Пока нет комментариев

Содержание

Что именно ломается в реальных проектахДве разные сущности: lint и formatНесогласованные конфиги в локальной среде и CIЛинтится больше файлов, чем нужноБазовая концепция: один источник истины и одна точка примененияНастройка Ruff: конфигурация под единый стильМинимальный рабочий pyproject.tomlЛокальная команда “приведи к стандарту”Что важно для согласованностиФорматирование vs линтинг: как избежать цикла “туда-сюда”Почему вообще возникает циклСтратегия “одного форматтера”Миграция существующего кода без “войны”Шаг 1: начать с “текущего состояния” (baseline)Шаг 2: использовать “мягкие” ограничения на миграциюШаг 3: поэтапное ужесточениеКак не увеличить время проверок в CI1) Ограничивайте область проверки2) Используйте кэш (по умолчанию Ruff его использует)3) Не запускайте авто-фиксы в CI (или делайте это только в режиме “быстро починили”)4) Сведите команды в один job и не дублируйте работуРецепт CI: проверка без “поломок” и с предсказуемым поведениемПочему это “мягче”, чем кажетсяСтратегии исключений, которые не убивают дисциплинуПравило №1: исключайте на уровне директории/паттерна, а не на уровне каждой строкиПравило №2: помечайте исключения как временныеПравило №3: не отключайте правила целиком без причиныЧастые подводные камни при внедрении Ruff форматированияПодводный камень: расхождение версий инструментаПодводный камень: разные Python-версииПодводный камень: форматирование без проверки --checkПодводный камень: слишком “широкая” стартовая конфигурацияПрактический план внедрения за 3–5 итерацийИтерация 1: диагностика и baselineИтерация 2: привести код к форматуИтерация 3: авто-фиксы lintИтерация 4: закрепить дисциплину в CIИтерация 5: ужесточение по правиламКогда стоит задуматься о обучающем материалеВыводы