$ sudo teach IT
Модуль 1 · Введение

Первый запуск

Запускаем Ruff на реальном коде и учимся читать вывод.

🛠️ Практика 🕐 ~20 минут

В предыдущем уроке мы установили Ruff. Теперь настало время запустить его на реальном коде и научиться интерпретировать результаты. В этом уроке мы подробно разберём, как работает команда ruff check, как Ruff обнаруживает файлы для проверки, что означает каждая часть вывода и как использовать различные флаги для управления поведением линтера. К концу урока вы сможете уверенно запускать Ruff на своих проектах, понимать его сообщения и эффективно исправлять найденные проблемы.

Ruff — это не просто линтер, но и форматтер. В этом уроке мы сосредоточимся на команде ruff check, которая отвечает за линтинг (поиск ошибок и нарушений стиля). Команда ruff format будет подробно рассмотрена в отдельном уроке. Однако некоторые аспекты форматирования мы также затронем, так как они тесно связаны с линтингом.

▶️

Запуск на проекте

Базовый синтаксис команды ruff check очень прост: вы указываете путь к файлу или директории, которую хотите проверить, и Ruff анализирует все Python-файлы по указанному пути. Вот несколько примеров базового использования:

Терминал
# Проверить все файлы в текущей директории (рекурсивно)
ruff check .

# Проверить конкретную директорию
ruff check src/
ruff check src/utils/
ruff check tests/

# Проверить один файл
ruff check main.py
ruff check /absolute/path/to/file.py

# Проверить несколько файлов
ruff check main.py utils.py models.py

# Проверить по glob-паттерну (через shell)
ruff check src/**/*.py
ruff check tests/test_*.py

# Комбинирование путей
ruff check src/ tests/ main.py

# Проверить и применить исправления
ruff check . --fix

# Проверить в режиме "только предупреждения"
ruff check . --show-fixes

Обратите внимание: если вы не укажете ни одного пути, Ruff не будет знать, что проверять, и вы получите сообщение об ошибке. Всегда указывайте хотя бы один путь. Самый распространённый вариант — ruff check . (точка означает текущую директорию). Ruff обходит все поддиректории рекурсивно, находя файлы с расширением .py (а также .pyi для stub-файлов).

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

В реальных проектах часто нужно проверять несколько директорий одновременно. Например, сам код находится в src/, тесты в tests/, а скрипты в корне проекта. Вы можете передать все эти пути одной командой:

Терминал
# Проверка нескольких директорий
ruff check src/ tests/ scripts/

# Исключение директории (если нужно пропустить)
ruff check . --exclude .venv --exclude __pycache__

# Использование .gitignore (по умолчанию включено)
ruff check . --respect-gitignore
# Ruff по умолчанию уважает .gitignore, так что --respect-gitignore избыточен

# Игнорировать .gitignore (проверить всё, включая игнорируемые файлы)
ruff check . --no-respect-gitignore
🔍

Как Ruff обнаруживает файлы

Понимание того, как Ruff находит файлы для проверки, важно для эффективного использования инструмента. Когда вы запускаете ruff check ., Ruff выполняет следующие шаги:

  1. Обход директории — Ruff рекурсивно обходит все поддиректории, начиная с указанного пути. Для каждой директории Ruff смотрит на список файлов и фильтрует только те, которые имеют расширение .py или .pyi.
  2. Применение правил исключения — Ruff проверяет, не игнорируется ли файл или директория через файлы .gitignore, .ruffignore или через настройки exclude в конфигурационном файле. По умолчанию Ruff исключает стандартные директории: .venv, .git, __pycache__, node_modules, .mypy_cache, .pytest_cache, .ruff_cache и другие.
  3. Поиск конфигурации — для каждого найденного файла Ruff пытается найти ближайший конфигурационный файл (pyproject.toml, ruff.toml, .ruff.toml). Ruff поднимается вверх по дереву директорий, пока не найдет конфигурацию. Это позволяет иметь разную конфигурацию для разных частей проекта (например, для тестов и для production-кода).
  4. Линтинг — для каждого файла Ruff применяет все включённые правила и собирает список нарушений. Благодаря тому, что Ruff написан на Rust и использует многопоточность, он может обрабатывать сотни файлов в секунду.

Файлы .ruffignore

Вы можете создать файл .ruffignore в корне проекта, в котором перечислить файлы и директории, которые Ruff должен игнорировать. Формат такой же, как у .gitignore:

.ruffignore
# Игнорировать директорию сгенерированного кода
generated/

# Игнорировать конкретный файл
scripts/deprecated.py

# Игнорировать по glob-паттерну
**/migrations/*.py

# Игнорировать все __init__.py в определённой директории
src/legacy/*/__init__.py

Влияние .gitignore на поиск файлов

По умолчанию Ruff уважает правила .gitignore. Это означает, что если файл или директория исключены из Git через .gitignore, Ruff также не будет их проверять. Это поведение можно отключить с помощью флага --no-respect-gitignore. По умолчанию респект .gitignore включён, поэтому вам не нужно передавать никаких дополнительных флагов. Ruff также игнорирует бинарные файлы, файлы с расширениями, отличными от .py и .pyi, и символические ссылки, ведущие за пределы проекта.

Глубина обхода директорий

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

📋

Понимание вывода

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

Возьмём такой файл с несколькими типичными ошибками:

example.py
import os
import sys
import json  # не используется
from pathlib import Path

CONSTANT=42
unused_variable = "никто не использует эту переменную"

def my_function():
    x=1
    y =   2
    print( x )
    return None

Запускаем ruff check example.py и получаем:

example.py:3:8: F401 [*] `json` imported but unused
example.py:6:1: E302 Expected 2 blank lines after class or function definition, found 0
example.py:6:12: E231 Missing whitespace after ':'
example.py:8:5: F841 [*] Local variable `unused_variable` is assigned to but never used
example.py:11:4: E225 Missing whitespace around operator
example.py:12:8: E222 Multiple spaces after operator
example.py:13:11: E201 Whitespace after '('
example.py:13:16: E202 Whitespace before ')'
example.py:14:19: E711 Comparison to `None` should be `cond is None`
Found 9 errors.
[*] 2 fixable with the `--fix` option.

Теперь разберём каждую часть вывода подробно. Формат каждой строки нарушения выглядит так:

{file}:{line}:{col}: {rule_code} [{marker}] {message}
example.py:3:8 — путь к файлу, номер строки (3) и номер колонки (8). Ruff использует 1-индексацию как для строк, так и для колонок (в отличие от некоторых других инструментов, которые используют 0-индексацию для колонок). Номер колонки указывает на позицию, где начинается проблемный участок кода. В данном случае на 8-м символе строки 3 находится начало импорта json.
F401 — код правила. Первая буква или префикс указывает на источник правила: F = Pyflakes (ошибки, связанные с импортами, переменными и потоком выполнения), E = pycodestyle (нарушения стиля, пробелов, отступов), W = pycodestyle (предупреждения о стиле), N = pep8-naming (нарушения именования), D = pydocstyle (проблемы с документацией), I = isort (порядок импортов), UP = pyupgrade (устаревший синтаксис), SIM = flake8-simplify (упрощение кода), ANN = flake8-annotations (проблемы с аннотациями типов), INT = flake8-gettext, PTH = flake8-use-pathlib, TD = flake8-todos, RSE = flake8-raise, RET = flake8-return, TCH = flake8-type-checking, PL = pylint (некоторые правила), RUF = специфичные для Ruff правила. Число после префикса — номер конкретного правила. Вы можете найти подробное описание каждого правила в документации Ruff.
[*] — маркер автоматического исправления. Означает, что Ruff может исправить эту ошибку автоматически с помощью флага --fix. Не все ошибки можно исправить автоматически. Например, F401 (неиспользуемый импорт) помечен [*], но F841 (неиспользуемая переменная) тоже помечен, так как Ruff может удалить неиспользуемую переменную. Однако некоторые ошибки (например, E302 — пропущенные пустые строки) Ruff может не помечать [*], если контекст исправления неоднозначен. В выводе также есть итоговая строка, которая сообщает, сколько ошибок можно исправить автоматически.
`json` imported but unused — текстовое описание нарушения. Это человекочитаемое сообщение, которое объясняет, что именно не так. Сообщения Ruff лаконичны и информативны. Они написаны на английском языке, так как это стандарт для инструментов разработки Python. Если вам нужен перевод, вы можете найти описание правил на русском в документации сообщества.

Итоговая статистика

В конце вывода Ruff показывает итоговую статистику:

  • Found 9 errors. — общее количество найденных нарушений. Если ошибок нет, вы увидите Found 0 errors.. Ruff никогда не выводит сообщение "No errors found!" — он молчалив, когда всё хорошо.
  • [*] 2 fixable with the --fix option. — количество ошибок, которые можно исправить автоматически. Обратите внимание: не все ошибки можно безопасно исправить автоматически. Некоторые правила (например, связанные с семантикой кода) Ruff не может исправить, так как это может изменить поведение программы.

Если ошибок не найдено, вывода не будет вообще (кроме кода возврата 0). Это сделано намеренно: Ruff придерживается философии Unix "тишина — это успех". Если вам нужно явное подтверждение, используйте флаг --verbose или -v.

🎨

Форматы вывода

Ruff поддерживает несколько форматов вывода, которые можно выбрать с помощью флага --output-format. Это особенно полезно при интеграции с различными инструментами и CI-системами.

Поддерживаемые форматы

Терминал
# Текстовый формат (по умолчанию) — цветной, человекочитаемый
ruff check . --output-format text

# JSON — машинно-читаемый формат для интеграции с другими инструментами
ruff check . --output-format json

# JSON с подсветкой синтаксиса
ruff check . --output-format json-lines

# Concise — более краткий вывод (только имя файла, строка и код правила)
ruff check . --output-format concise

# GitHub — формат, совместимый с GitHub Actions (создаёт аннотации в PR)
ruff check . --output-format github

# GitLab — формат, совместимый с GitLab CI
ruff check . --output-format gitlab

# Pylint — формат, совместимый с pylint (для интеграции с существующими инструментами)
ruff check . --output-format pylint

# JUnit — формат для отчётов в CI (например, Jenkins)
ruff check . --output-format junit

# Full — максимально подробный вывод (аналогичен text, но с дополнительной информацией)
ruff check . --output-format full

Пример вывода в JSON

Формат JSON особенно полезен, если вы хотите обработать результаты линтинга программно (например, в скрипте или CI-пайплайне):

[
  {
    "cell": null,
    "code": "F401",
    "col": 8,
    "end_col": 12,
    "end_line": 3,
    "filename": "example.py",
    "fix": {
      "applicability": "safe",
      "message": "Remove unused import: `json`"
    },
    "line": 3,
    "message": "`json` imported but unused",
    "noqa_row": 3,
    "url": "https://docs.astral.sh/ruff/rules/unused-import"
  }
]

Как видите, JSON-формат содержит много полезной информации: точное расположение ошибки (строка, колонка, конечная строка, конечная колонка), код правила, сообщение, информацию о возможном автоматическом исправлении, а также ссылку на документацию. Поле url содержит прямую ссылку на описание правила на сайте Ruff, что очень удобно для быстрого перехода к документации.

Пример вывода в формате GitHub

Формат github создаёт аннотации, которые отображаются в GitHub Actions прямо в интерфейсе Pull Request. Это позволяет видеть ошибки линтинга без переключения в логи:

::warning file=example.py,line=3,col=8,endLine=3,endColumn=12::F401 `json` imported but unused

GitHub автоматически парсит этот формат и отображает предупреждения прямо в diff-вьювере Pull Request, что значительно упрощает code review.

⚡

Продвинутые примеры с множеством файлов

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

Предположим, у нас есть такая структура проекта:

my_project/
├── src/
│   ├── main.py
│   ├── utils/
│   │   ├── __init__.py
│   │   ├── helpers.py
│   │   └── parsers.py
│   └── models/
│       ├── user.py
│       └── product.py
├── tests/
│   ├── test_main.py
│   └── test_utils.py
├── scripts/
│   └── migrate.py
└── setup.py

Мы можем запустить Ruff на всём проекте одной командой:

Терминал
# Проверить весь проект
ruff check my_project/

# Или изнутри директории проекта
cd my_project
ruff check .

# Проверить только исходный код (без тестов и скриптов)
ruff check src/

# Проверить только тесты
ruff check tests/

# Проверить конкретные файлы
ruff check src/main.py src/utils/helpers.py

# Проверить с исключением тестов
ruff check . --exclude tests/

# Проверить с указанием нескольких корневых директорий
ruff check src/ tests/ scripts/

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

Терминал
# Отсортировать ошибки по имени файла
ruff check . | sort

# Посмотреть уникальные коды ошибок
ruff check . | awk '{print $3}' | sort -u

# Посчитать количество ошибок по кодам
ruff check . | awk '{print $3}' | sort | uniq -c | sort -rn
🔢

Коды возврата

Ruff использует коды возврата для сообщения о результатах своей работы. Это особенно важно для CI/CD-систем, которые полагаются на код возврата для определения успешности шага сборки. Понимание кодов возврата помогает правильно настроить CI-пайплайн и интерпретировать результаты линтинга.

Код Значение Описание
0 Успех Ошибок не найдено. Всё чисто.
1 Найдены ошибки Одна или более ошибок найдены. CI-шаг должен упасть.
2 Внутренняя ошибка Ruff не смог завершить работу из-за внутренней проблемы (например, неверный синтаксис конфигурации, проблемы с чтением файла, неверные аргументы командной строки, ошибка парсинга Python-файла).

Давайте проверим это на практике:

Терминал
# Чистый файл — код возврата 0
echo "x = 1" > /tmp/clean.py
ruff check /tmp/clean.py
echo "Код возврата: $?"
# Вывод: Код возврата: 0

# Файл с ошибкой — код возврата 1
echo "x=1" > /tmp/dirty.py
ruff check /tmp/dirty.py
echo "Код возврата: $?"
# Вывод: Код возврата: 1

# Несуществующий файл — код возврата 2
ruff check /tmp/nonexistent.py 2>/dev/null
echo "Код возврата: $?"
# Вывод: Код возврата: 2

# Неправильный аргумент — код возврата 2
ruff check --nonexistent-flag 2>/dev/null
echo "Код возврата: $?"
# Вывод: Код возврата: 2

rm /tmp/clean.py /tmp/dirty.py

В Bash/Shell вы можете проверить код возврата последней выполненной команды через переменную $?. В CI-системах код возврата проверяется автоматически: если команда вернула ненулевой код, шаг считается неудачным. Однако вы также можете явно управлять поведением с помощью флагов, описанных ниже.

Флаг --exit-zero

Флаг --exit-zero заставляет Ruff всегда возвращать код 0, независимо от того, найдены ли ошибки. Это полезно в следующих ситуациях:

  • Вы только начинаете внедрять Ruff в существующий проект с большим количеством старых ошибок и не хотите блокировать CI.
  • Вы хотите собирать статистику по ошибкам, не прерывая сборку.
  • Вы используете Ruff в качестве дополнительного (не обязательного) инструмента в CI.
Терминал
# Всегда возвращать 0, даже если есть ошибки
ruff check . --exit-zero

# Проверить код возврата
ruff check . --exit-zero
echo $?  # Всегда будет 0

Флаг --exit-non-zero-on-fix

Флаг --exit-non-zero-on-fix изменяет поведение: Ruff возвращает код 1, если были применены какие-либо автоматические исправления. Это полезно для рабочего процесса, где вы хотите сначала запустить Ruff с --fix, а затем повторно запустить проверку, чтобы убедиться, что после исправлений не осталось ошибок:

Терминал
# Применить исправления и получить код 1, если что-то было исправлено
ruff check . --fix --exit-non-zero-on-fix
if [ $? -eq 1 ]; then
  echo "Были применены исправления. Код был изменён."
fi

# Типичный сценарий в CI:
# 1. Сначала применяем исправления
ruff check . --fix --exit-non-zero-on-fix
# 2. Если были исправления, коммитим их или сообщаем об ошибке
# 3. Затем проверяем, что после исправлений всё чисто
ruff check .

Этот подход позволяет автоматически исправлять то, что можно исправить, и при этом сигнализировать об изменениях в коде. В CI такой сценарий обычно означает, что после применения исправлений нужно создать новый коммит или сообщить разработчику о необходимости запустить ruff check --fix локально.

📥

Работа с stdin

Ruff поддерживает чтение кода из стандартного ввода (stdin). Это полезно для интеграции с редакторами кода (через LSP), pre-commit хуками и пайплайнами обработки данных. Когда вы передаёте код через stdin, Ruff не знает имени файла, поэтому вы должны указать его с помощью флага --stdin-filename. Имя файла используется для определения языка (по расширению) и для правильного отображения путей в выводе.

Синтаксис использования:

Терминал
# Проверить код из stdin
echo "x=1" | ruff check --stdin-filename example.py -

# С форматированием
echo "x=1" | ruff format --stdin-filename example.py -

# Проверка многострочного кода из stdin
cat << 'EOF' | ruff check --stdin-filename test.py -
import os
import sys
import json

x=1
print( x )
EOF

Важные моменты при работе с stdin:

  • Всегда указывайте --stdin-filename — без него Ruff не сможет определить, какие правила применять (некоторые правила зависят от имени файла, например, __init__.py обрабатывается особым образом).
  • Дефис (-) в конце — обозначает чтение из stdin. Это стандартное соглашение Unix.
  • Вывод для форматирования — при использовании ruff format с stdin, Ruff выводит отформатированный код в stdout, что позволяет использовать его в пайпах: echo "x=1" | ruff format --stdin-filename example.py -.
  • Режим только проверки — при использовании ruff check с stdin, Ruff выводит список ошибок в stdout (как обычно), а не исправленный код. Для получения исправленного кода через stdin используйте ruff check --fix --stdin-filename example.py -.

Пример интеграции с pre-commit (через stdin):

Терминал
# Использование в pre-commit: проверяем только изменённые файлы
git diff --cached --name-only --diff-filter=ACM | \
  grep '\.py$' | \
  xargs -r ruff check --stdin-filename

# Или для каждого файла по отдельности
git diff --cached --name-only --diff-filter=ACM | \
  grep '\.py$' | \
  while read file; do
    git show ":$file" | ruff check --stdin-filename "$file" -
  done
📜

Понимание кодов правил

Каждое нарушение, которое находит Ruff, имеет уникальный код. Эти коды организованы по префиксам, которые указывают на источник правила. Понимание этих префиксов помогает быстро определить тип проблемы и найти соответствующую документацию. Всего Ruff поддерживает более 800 правил (и это число постоянно растёт), разделённых на категории.

Префикс Источник Пример Описание
F Pyflakes F401, F841, F821 Логические ошибки: неиспользуемые импорты, переменные, неопределённые имена
E / W pycodestyle E225, E302, W291 Нарушения стиля: пробелы, отступы, пустые строки, завершающие пробелы
N pep8-naming N802, N803, N806 Нарушения именования: функции, классы, переменные не в том регистре
D pydocstyle D100, D200, D400 Проблемы с документацией: отсутствие или неправильный формат docstring
I isort I001, I002 Неправильный порядок импортов
UP pyupgrade UP007, UP030 Устаревший синтаксис Python: использование старых возможностей языка
SIM flake8-simplify SIM101, SIM108 Возможности упростить код
ANN flake8-annotations ANN001, ANN201 Отсутствие аннотаций типов у аргументов и возвращаемых значений
PT flake8-pytest-style PT001, PT006 Стилистические проблемы в pytest-тестах
PL Pylint PLR2004, PLC0206 Различные проверки из Pylint (магические числа, присваивание в условиях и т.д.)
RUF Ruff-specific RUF001, RUF100 Специфичные для Ruff правила: небезопасные символы, неиспользуемые подавления noqa

Как узнать, что делает правило

Есть несколько способов получить информацию о конкретном правиле Ruff:

Терминал
# Получить описание правила
ruff rule F401

# Получить описание с примерами и ссылками
ruff rule E225 --format json

# Список всех правил с краткими описаниями
ruff rule --all

# Поиск правил по ключевому слову
ruff rule --all | grep -i "import"
ruff rule --all | grep "unused"

# Фильтр по префиксу
ruff rule --all | grep "^F" | head -10

Команда ruff rule — это ваш лучший друг для изучения правил. Она показывает полное описание правила, включая примеры правильного и неправильного кода, а также ссылку на документацию. Это гораздо удобнее, чем искать информацию в интернете. Если вы не понимаете, почему Ruff показывает ту или иную ошибку, просто выполните ruff rule <CODE>, и вы получите исчерпывающее объяснение.

🏴

Полезные флаги командной строки

Ruff предоставляет множество флагов для настройки поведения команд. Вот наиболее полезные из них для повседневной работы:

Флаг Описание
--fix Автоматически исправлять ошибки, которые можно исправить безопасно. После применения исправлений Ruff показывает только те ошибки, которые не удалось исправить. Если вы хотите увидеть все ошибки, включая исправленные, используйте --show-fixes.
--show-fixes Показывать, какие исправления были применены. Работает вместе с --fix. Выводит список всех применённых исправлений с указанием файла, строки и типа исправления.
--diff Показать diff исправлений, не применяя их. Полезно для проверки, что именно изменит Ruff, перед тем как применять исправления. Вывод выглядит как обычный git-diff.
--select Выбрать только определённые правила для проверки. Например, --select F401,E225 проверит только эти два правила. Можно использовать префиксы: --select F для всех правил Pyflakes.
--ignore Игнорировать определённые правила. Например, --ignore E225 пропустит проверку пробелов вокруг операторов. Можно комбинировать с --select.
--extend-select Добавить правила к стандартному набору, не заменяя его. Например, --extend-select D добавит все правила pydocstyle к стандартному набору.
--per-file-ignores Игнорировать правила для конкретных файлов или паттернов. Например, --per-file-ignores "__init__.py:F401" разрешит неиспользуемые импорты в __init__.py.
--exclude Исключить файлы/директории из проверки. Можно указывать паттерны. Например, --exclude "**/migrations/*".
--respect-gitignore / --no-respect-gitignore Уважать или игнорировать .gitignore. По умолчанию включено.
--output-format Формат вывода: text, json, json-lines, concise, github, gitlab, pylint, junit, full.
--target-version Версия Python, для которой проверяется код. По умолчанию Ruff использует версию Python, которой он был установлен, но вы можете указать другую.
--max-line-length Максимальная длина строки (по умолчанию 88 символов, как в Black). Увеличьте, если ваш проект использует другую длину строки.
--unsafe-fixes Применять также "небезопасные" исправления, которые могут изменить поведение кода. По умолчанию Ruff применяет только безопасные исправления.
--no-cache Отключить кэширование. Ruff кэширует результаты линтинга для ускорения повторных запусков. Если вы подозреваете, что кэш устарел, используйте этот флаг или выполните ruff clean.
--add-noqa Добавить # noqa комментарии для всех найденных ошибок. Полезно, когда нужно "заморозить" текущее состояние и постепенно исправлять ошибки.

Комбинации флагов для типовых сценариев

Терминал
# Проверить только новые правила (не включённые в стандартный набор)
ruff check . --select ALL --ignore E,W,F,N  # все, кроме стандартных

# Посмотреть diff исправлений без применения
ruff check . --diff

# Применить только безопасные исправления (по умолчанию)
ruff check . --fix

# Применить все исправления, включая небезопасные
ruff check . --fix --unsafe-fixes

# Проверить с отключением кэша
ruff check . --no-cache

# Добавить noqa для всех текущих ошибок (заморозка)
ruff check . --add-noqa

# Игнорировать ошибки в тестовых файлах
ruff check . --per-file-ignores "tests/*.py:D100,D200"

# Проверить с максимальной длиной строки 120
ruff check . --max-line-length 120

# Комбинация: показать diff в компактном формате
ruff check . --diff --output-format concise
🎬

Типовые сценарии первого запуска

Давайте рассмотрим несколько типовых сценариев, с которыми вы можете столкнуться при первом запуске Ruff на реальном проекте. Каждый сценарий требует своего подхода.

Сценарий 1: Новый проект с нуля

Вы только что создали новый проект и хотите сразу настроить Ruff для поддержания качества кода. В этом случае всё просто:

Терминал
# Создать новый файл
echo 'print("hello world")' > main.py

# Запустить Ruff
ruff check main.py
# Вывод: Found 0 errors.

# Всё чисто с первого раза! Ruff не находит ошибок.
# Ruff считает строку "print("hello world")" абсолютно корректной,
# хотя по PEP 8 рекомендуется использовать скобки и в Python 3
# print — это функция, так что всё верно.

Сценарий 2: Существующий проект с парой ошибок

Вы запускаете Ruff на существующем проекте и получаете несколько десятков ошибок. Это нормальная ситуация, даже для хорошо написанного кода — многие ошибки связаны с тривиальными нарушениями стиля (лишние пробелы, отсутствие пустых строк и т.д.):

Терминал
# Запуск на проекте
ruff check src/

# Видим несколько десятков ошибок. Что делать?
# 1. Применить автоматические исправления
ruff check src/ --fix

# 2. Запустить повторную проверку
ruff check src/
# Осталось меньше ошибок (те, что нельзя исправить автоматически)

Сценарий 3: Большой проект с сотнями ошибок

Вы запускаете Ruff на большом проекте, который не линтился годами. Получаете 500+ ошибок. Не паникуйте! Вот стратегия пошагового внедрения:

Терминал
# Шаг 1: Посмотреть общую картину
ruff check . --output-format concise | head -20

# Шаг 2: Посмотреть, какие типы ошибок преобладают
ruff check . | awk '{print $3}' | sort | uniq -c | sort -rn | head -10

# Шаг 3: Исправить автоматически всё, что можно
ruff check . --fix

# Шаг 4: Создать baseline для остальных ошибок
ruff check . --add-noqa  # добавит # noqa для всех текущих ошибок

# Шаг 5: Настроить CI только для новых ошибок
# Теперь новые изменения будут проверяться Ruff,
# а старые ошибки будут скрыты за noqa

# Шаг 6: Постепенно исправлять старые ошибки
# Удаляйте noqa по мере исправления ошибок

Альтернативный подход — начать с малого количества правил и постепенно расширять:

Терминал
# Сначала только критические ошибки (F = Pyflakes)
ruff check . --select F

# Добавить ошибки стиля
ruff check . --select F,E

# Добавить именование
ruff check . --select F,E,N

# И так далее, пока не включите все нужные правила

Сценарий 4: Проект с автогенерированным кодом

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

.ruffignore
# Автогенерированный код
gen/
proto/
migrations/

# Сторонние зависимости
vendor/
third_party/

# Конфигурационные файлы
config/*.py

Сценарий 5: Запуск в pre-commit хуке

Pre-commit хуки проверяют только изменённые файлы, что значительно ускоряет работу по сравнению с проверкой всего проекта. Ruff имеет официальную интеграцию с pre-commit:

.pre-commit-config.yaml
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.8.0
    hooks:
      - id: ruff
        args: [--fix, --exit-non-zero-on-fix]
      - id: ruff-format

Если вы не используете pre-commit, вы можете вручную проверять только изменённые файлы:

Терминал
# Проверить только изменённые файлы (незакоммиченные)
git diff --name-only | grep '\.py$' | xargs ruff check

# Проверить только файлы в staging
git diff --cached --name-only | grep '\.py$' | xargs ruff check

# Проверить изменения относительно main
git diff main --name-only | grep '\.py$' | xargs ruff check
⭐

Понимание маркера [*]

Маркер [*] — один из самых полезных элементов вывода Ruff. Он указывает, что данное нарушение может быть исправлено автоматически при запуске с флагом --fix. Понимание того, какие ошибки можно исправить автоматически, а какие требуют ручного вмешательства, поможет вам эффективно работать с Ruff.

Категории исправлений

Ruff делит автоматические исправления на три категории:

  • Безопасные (safe) — помечены как [*]. Эти исправления гарантированно не изменяют поведение кода. Например: удаление неиспользуемого импорта, добавление пробелов вокруг оператора, удаление лишних пробелов. Ruff применяет только безопасные исправления по умолчанию.
  • Небезопасные (unsafe) — помечены как [*] только с флагом --unsafe-fixes. Эти исправления могут изменить поведение кода в редких случаях. Например: замена type() на isinstance(), упрощение логических выражений. Ruff не применяет их без явного разрешения.
  • Не исправляемые — не имеют маркера [*]. Эти нарушения нельзя исправить автоматически, так как для их исправления требуется понимание семантики кода. Например: неправильное именование переменной, отсутствие документации, неиспользуемая переменная (которую можно было бы удалить, но Ruff не уверен, что это безопасно).

Вот несколько примеров ошибок с маркером [*] и без него:

main.py:1:1: E402 [*] Module level import not at top of file
main.py:3:8: F401 [*] `json` imported but unused
main.py:5:7: E225 [*] Missing whitespace around operator
main.py:7:1: E302      Expected 2 blank lines after class or function definition, found 0
main.py:9:5: N802      Function name should be lowercase
main.py:11:1: D100     Missing docstring in public module

Как видите, ошибки E302, N802 и D100 не имеют маркера [*]. Ruff не может автоматически добавить пустые строки (E302), так как не знает, сколько их должно быть в данном контексте. Он не может переименовать функцию (N802), так как это изменит API. И он не может написать документацию (D100), так как это требует понимания назначения модуля.

Как использовать --fix

Флаг --fix применяет все безопасные исправления. После применения Ruff показывает только те ошибки, которые остались (не были исправлены). Это позволяет быстро очистить код от тривиальных нарушений и сосредоточиться на более серьёзных проблемах:

Терминал
# Применить безопасные исправления
ruff check . --fix

# Применить все исправления, включая небезопасные
ruff check . --fix --unsafe-fixes

# Посмотреть diff перед применением
ruff check . --diff

# Применить исправления и показать, что было исправлено
ruff check . --fix --show-fixes

Рекомендуется всегда сначала запускать ruff check . --diff, чтобы увидеть, что именно изменит Ruff. После этого выполните ruff check . --fix, если diff выглядит корректно. В CI рекомендуется сначала запускать ruff check . --diff и, если есть изменения, завершать шаг с ошибкой — это гарантирует, что все изменения прошли через линтер.

✅

Когда ошибок нет

Когда Ruff не находит ни одной ошибки, он ведёт себя очень тихо: просто завершается с кодом 0 и не выводит ничего в stdout. Это соответствует философии Unix "тишина — это успех". Однако такое поведение может сбивать с толку новых пользователей: "Ruff ничего не вывел, значит, он сломался?" Нет, это значит, что ваш код идеален с точки зрения Ruff.

Если вам нужно явное подтверждение, что Ruff выполнился успешно, используйте флаг --verbose или -v:

Терминал
# Создадим чистый файл
echo -e "x = 1\nprint(x)" > clean.py

# Без verbose — тишина
ruff check clean.py
# (ничего не выводит)

# С verbose
ruff check clean.py --verbose
# Вывод:
# [INFO] Checking file: clean.py
# [INFO] No errors found

# Проверим код возврата
ruff check clean.py
echo $?  # 0

rm clean.py
🔥

Что делать, если Ruff находит сотни ошибок

Это самая частая проблема при первом запуске Ruff на существующем проекте. Не пугайтесь — это нормально, особенно если проект не использовал линтер ранее или использовал другой линтер с другими настройками. Сотни ошибок не означают, что код плох; они означают, что в проекте есть много мелких нарушений стиля, которые накопились со временем. Вот пошаговая стратегия, как справиться с этой ситуацией:

Шаг 1: Оцените масштаб

Терминал
# Сколько всего ошибок?
ruff check . | tail -5
# Ищем строку "Found X errors."

# Какие ошибки самые частые?
ruff check . | awk '{print $3}' | sort | uniq -c | sort -rn | head -20

# Сколько файлов с ошибками?
ruff check . | awk -F':' '{print $1}' | sort -u | wc -l

# Какие файлы содержат больше всего ошибок?
ruff check . | awk -F':' '{print $1}' | sort | uniq -c | sort -rn | head -10

Шаг 2: Автоматически исправьте всё, что можно

Терминал
# Применить безопасные исправления
ruff check . --fix

# Это исправит большую часть ошибок (пробелы, импорты, синтаксис)
# После этого запустите проверку снова

# Сколько осталось?
ruff check . | tail -5

Шаг 3: Внедряйте постепенно

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

  • Стратегия A: По типу ошибок — Начните с критических ошибок (Pyflakes, F), затем добавляйте стилевые (pycodestyle, E/W), затем именование (N) и так далее.
  • Стратегия B: По директориям — Начните с одной директории, приведите её в порядок, затем переходите к следующей.
  • Стратегия C: Только новый код — Используйте --add-noqa чтобы заморозить текущие ошибки, и настройте CI так, чтобы он проверял только новые изменения.
  • Стратегия D: Git-based — Проверяйте только изменённые файлы. В CI это обычно означает проверку файлов, изменённых в Pull Request.

Стратегия A: Постепенное включение правил

Терминал
# Этап 1: Только логические ошибки (Pyflakes)
ruff check . --select F --fix

# Этап 2: Добавить стилевые ошибки (pycodestyle)
ruff check . --select F,E --fix

# Этап 3: Добавить именование (pep8-naming)
ruff check . --select F,E,N --fix

# Этап 4: Добавить импорты (isort)
ruff check . --select F,E,N,I --fix

# Этап 5: Полный набор
ruff check . --select ALL --fix

Стратегия C: Заморозка текущих ошибок через add-noqa

Команда ruff check --add-noqa добавляет комментарий # noqa к каждой строке с ошибкой. Это позволяет "заморозить" текущие ошибки и начать отслеживать только новые. По мере исправления ошибок вы будете удалять соответствующие # noqa комментарии:

Терминал
# Заморозить текущие ошибки
ruff check . --add-noqa

# После этого все строки с ошибками будут помечены # noqa
# Теперь CI будет проверять только новые ошибки

# Постепенно исправляйте файлы и удаляйте noqa
# Например, исправить файл и проверить, что noqa больше не нужны
ruff check src/utils/helpers.py  # должно быть 0 ошибок (все скрыты noqa)
# После исправления удалите noqa и проверьте снова

Стратегия D: Проверка только изменённого кода

Эта стратегия особенно эффективна, если вы не можете (или не хотите) исправлять существующий код, но хотите гарантировать качество нового кода:

Терминал
# В CI: проверять только файлы, изменённые в PR
# GitHub Actions предоставляет список изменённых файлов
# Пример скрипта для любого CI:
CHANGED_FILES=$(git diff --name-only HEAD~1 | grep '\.py$' || true)
if [ -n "$CHANGED_FILES" ]; then
  ruff check $CHANGED_FILES
fi

Общие рекомендации

  • Не пытайтесь исправить всё за один день. Даже если у вас 1000+ ошибок, это не критично. Большинство из них — тривиальные нарушения стиля, которые не влияют на работу кода.
  • Автоматизируйте исправления. После того как вы настроили Ruff, добавьте его в pre-commit хук или CI, чтобы новые ошибки не появлялись. Постепенно количество старых ошибок будет уменьшаться по мере того, как вы будете трогать соответствующие файлы.
  • Используйте --fix в pre-commit хуках. Это позволит автоматически исправлять тривиальные ошибки при каждом коммите.
  • Не игнорируйте все ошибки сразу. Лучше использовать --select, чтобы начать с малого, чем --ignore, чтобы выключить все правила.
  • Настройте конфигурацию под ваш проект. Возможно, некоторые правила Ruff не подходят для вашего проекта. Например, в проектах с Django часто отключают правила для файлов миграций.
✏️

Практическое задание

Чтобы закрепить материал, выполните следующие шаги:

  1. Создайте файл test_example.py со следующим содержимым (скопируйте код ниже).
  2. Запустите ruff check test_example.py и изучите вывод.
  3. Запустите ruff check test_example.py --fix и посмотрите, какие ошибки исправились.
  4. Запустите ruff check test_example.py --diff (перед исправлениями), чтобы увидеть diff.
  5. Попробуйте ruff check test_example.py --select F — проверьте только логические ошибки.
  6. Попробуйте ruff check test_example.py --ignore E225 — проигнорируйте одну из ошибок.
test_example.py
import os
import sys
import json
from collections import OrderedDict
from pathlib import *

MY_CONSTANT=42
unused = "эта переменная не используется"

def my_function():
    x=1
    y =   2
    print( x, y )
    if x == 42:
        pass
    return None

class test_class:
    pass

Ожидаемый вывод (может незначительно отличаться в зависимости от версии Ruff):

test_example.py:1:1: I001 [*] Import block is un-sorted or un-formatted
test_example.py:3:8: F401 [*] `json` imported but unused
test_example.py:4:1: I001 [*] Import block is un-sorted or un-formatted
test_example.py:5:1: F403 [*] `from pathlib import *` used; unable to detect undefined names
test_example.py:7:1: E302      Expected 2 blank lines after class or function definition, found 0
test_example.py:7:14: E231 [*] Missing whitespace after ':'
test_example.py:8:5: F841 [*] Local variable `unused` is assigned to but never used
test_example.py:11:4: E225 [*] Missing whitespace around operator
test_example.py:12:8: E222 [*] Multiple spaces after operator
test_example.py:13:11: E201 [*] Whitespace after '('
test_example.py:13:16: E202 [*] Whitespace before ')'
test_example.py:17:1: E302      Expected 2 blank lines after class or function definition, found 0
test_example.py:17:14: E231 [*] Missing whitespace after ':'
test_example.py:19:1: N801      Class name `test_class` should use CapWords convention
Found 14 errors.
[*] 10 fixable with the `--fix` option.
🎯

Что дальше?

Поздравляем! Теперь вы умеете:

  • Запускать Ruff на одном или нескольких файлах/директориях.
  • Понимать формат вывода: файл, строка, колонка, код правила, маркер [*], сообщение.
  • Использовать коды возврата (0, 1, 2) и флаги --exit-zero, --exit-non-zero-on-fix.
  • Работать с stdin через --stdin-filename.
  • Понимать, как Ruff обнаруживает файлы (рекурсивный обход, .gitignore, .ruffignore).
  • Выбирать формат вывода (text, json, github, concise и другие).
  • Использовать флаги --select, --ignore, --fix, --diff, --add-noqa.
  • Внедрять Ruff в существующий проект с сотнями ошибок.

В следующем уроке мы узнаем, как настраивать Ruff под свои нужды: выбирать правила, игнорировать файлы, настраивать длину строки и многое другое.

Урок 1.3: Первый запуск

5 вопросов