$ sudo teach IT
Модуль 2 · Линтинг

ruff check — базовая проверка

Флаги, фильтрация правил и статистика.

🔧 Практика 🕐 ~30 минут
📖

Что такое ruff check

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

Ruff включает более 800 правил, сгруппированных по категориям. Каждое правило имеет уникальный код, состоящий из префикса категории и номера. Например, F401 означает категорию Pyflakes (F), правило 401 — импортирован, но не используется.

💡 Ruff написан на Rust и работает в десятки раз быстрее традиционных линтеров. На среднем проекте проверка занимает миллисекунды, а не секунды.
📂

Категории правил

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

Префикс Категория Источник Описание
E pycodestyle pycodestyle Ошибки стиля: лишние пробелы, пустые строки, длина строк
W pycodestyle pycodestyle Предупреждения стиля (менее строгие, чем E)
F Pyflakes Pyflakes Логические ошибки: неиспользуемые импорты, неопределённые переменные
I isort isort Сортировка импортов
N pep8-naming pep8-naming Именование: snake_case, CamelCase, константы
UP pyupgrade pyupgrade Современный синтаксис: f-строки, set literal, union types
B flake8-bugbear flake8-bugbear Баги и подозрительный код: мутация аргументов по умолчанию
S flake8-bandit bandit Безопасность: SQL инъекции, shell injection, опасные функции
SIM flake8-simplify flake8-simplify Упрощение кода: замена сложных конструкций на простые
ANN flake8-annotations flake8-annotations Тип-хинты: отсутствие аннотаций функций
PT flake8-pytest-style pytest-style Стиль тестов pytest: assert вместо self.assert*, fixture naming
D pydocstyle pydocstyle Документация: docstring-и, их формат и содержание

Каждая категория включает десятки правил. Например, категория F (Pyflakes) включает: F401 (неиспользуемый импорт), F402 (импорт, скрывающий имя из внешней области), F403 (from import * — неопределённые имена) и так далее.

🚀

Базовый запуск

Запустим проверку на демо-файле. Создайте файл example.py:

import os          # неиспользуемый импорт
import sys

x = 1
if x == 1:
    print("hello")

y =  [1,2,3]       # лишние пробелы
z = x+1            # пробелы вокруг оператора

class myclass:     # неправильное именование
    pass

Запускаем:

ruff check example.py

Вывод будет выглядеть так:

example.py:1:1: F401 `os` imported but unused
example.py:2:1: I001 Missing required import: `os` should be placed after `sys`
example.py:10:10: E201 Whitespace after '['
example.py:10:14: E202 Whitespace before ']'
example.py:11:7: E225 Missing whitespace around operator
example.py:13:7: N801 Class name `myclass` should use CapWords convention

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

🎯

--select и --ignore

Флаги --select и --ignore позволяют управлять правилами прямо из командной строки без конфигурационного файла. Это удобно для быстрых проверок и CI-скриптов.

--select — включает только указанные правила. Все остальные будут проигнорированы:

ruff check --select E .          # только pycodestyle ошибки
ruff check --select F401 .       # только конкретное правило
ruff check --select E,F,I .      # несколько категорий
ruff check --select ALL .        # включить все правила
ruff check --select E,W,F .      # смесь категорий

--ignore — исключает указанные правила из проверки. Всё остальное проверяется:

ruff check --ignore E501 .       # всё кроме E501 (слишком длинные строки)
ruff check --ignore F401,F841 .   # не проверять неиспользуемые импорты и переменные
ruff check --ignore E,W .         # игнорировать все стилистические правила
⚠️ --select ALL включит очень много правил — более 800. Часть из них может конфликтовать или быть излишне строгой. Использовать осторожно, особенно на больших проектах.
🧠

Как работает выбор правил

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

Флаг / Конфиг Поведение
select Полностью заменяет набор правил. Указываются только те правила, которые должны проверяться. Всё остальное отключается.
extend-select Добавляет правила к тем, что уже включены (по умолчанию или из select). Не заменяет, а расширяет.
ignore Исключает конкретные правила из текущего набора. Применяется после select и extend-select.
fixable / unfixable Управляют тем, какие правила могут автоматически исправлять код. Независимо от выбора правил.

Порядок обработки: сначала Ruff собирает базовый набор select, затем добавляет extend-select, затем применяет ignore. Финальный набор используется для проверки.

# В конфиге: select = ["E", "F", "I"]
# Из командной строки:
ruff check --extend-select B --ignore E501 .
# Результат: E, F, I, B — все кроме E501
# (потому что ignore вырезает E501 из набора)

# Полная замена через CLI:
ruff check --select ALL --ignore E501,W .
# Только ALL кроме E501 и W

Важный нюанс: select в CLI полностью переопределяет select из конфиг-файла. Если вам нужно добавить правила к конфигу, используйте --extend-select.

➕

--extend-select

--extend-select — это ключевое отличие от --select. Он добавляет правила к уже существующему набору, не заменяя его. Это особенно полезно, когда у вас есть конфигурационный файл с базовым набором правил, а в CLI вы хотите временно добавить дополнительные проверки:

# добавить проверки безопасности к дефолтным правилам
ruff check --extend-select S .

# добавить isort + bugbear
ruff check --extend-select I,B .

# добавить проверки документации и типов
ruff check --extend-select D,ANN .

# комбинация: безопасность + упрощение кода
ruff check --extend-select S,SIM .

# в конфиге: select = ["E", "F", "I"]
# в CLI добавляем:
ruff check --extend-select UP .
# итог: E, F, I, UP

select vs extend-select: таблица сравнения

Характеристика select extend-select
Действие Заменяет весь набор правил Добавляет к существующему набору
CLI переопределяет конфиг Да, полностью Нет, дополняет
Использование в CI Чёткий фиксированный набор Добавление временных проверок
Можно использовать несколько раз Последний перезаписывает предыдущие Все значения объединяются
Работа с ignore Ignore применяется после select Ignore применяется после extend-select
Рекомендуемый сценарий Определение набора в конфиге Временное добавление из CLI
💡 Правило большого пальца: используй select в конфигурационном файле для постоянного набора, а --extend-select в CLI для временных проверок.
💻

Примеры с реальным кодом

Рассмотрим реальные примеры кода и то, какие правила Ruff к ним применит.

Пример 1: Неиспользуемые импорты и переменные

import math       # F401: неиспользуемый импорт
import random
from datetime import datetime

def calculate():
    unused_var = 42  # F841: присвоено, но не используется
    x = random.randint(1, 10)
    now = datetime.now()
    print(f"Random number: {x}")
    # math не используется — F401

Пример 2: Проблемы безопасности

import subprocess  # S404: опасный модуль

user_input = "some; rm -rf /"
# S605: shell=True — инъекция
subprocess.call(f"echo {user_input}", shell=True)

password = "secret123"  # S105: пароль в коде

eval("print('hello')")  # S307: опасный eval

Пример 3: Баги и подозрительный код

def append_to(element, target=[]):  # B006: изменяемый аргумент по умолчанию
    target.append(element)
    return target

result = append_to(1)  # [1]
result = append_to(2)  # [1, 2] — баг! список сохраняется между вызовами

def check_value(x):
    if x == None:  # E711: сравнение с None через ==
        return False
    if x is not None:
        if x > 10:
            return True
    return False

# B007: переменная цикла не используется
for i in range(10):
    print("hello")

Пример 4: Именование (PEP8)

class person:     # N801: должно быть CapWords (Person)
    pass

def DoSomething():   # N802: должно быть snake_case (do_something)
    pass

MY_CONSTANT = 10   # правильно
myConstant = 20    # N815: mixCase в глобальной области

class MyClass:
    def getData(self):  # N802: должно быть get_data
        return self._data
💡 Чтобы увидеть полный список правил и их описания, выполни ruff check --help или посети официальную документацию на docs.astral.sh/ruff.
📊

--statistics — статистика нарушений

Флаг --statistics выводит сводку по каждому правилу: сколько раз оно было нарушено. Это невероятно полезно, когда вы начинаете линтить большой существующий проект — сразу видно, какие правила нарушаются чаще всего, и можно принять решение: исправлять массово или временно отключить.

ruff check --statistics .
# F401  12  `module` imported but unused
# E501   8  Line too long (88 > 79 characters)
# I001   5  Import block is unsorted
# F841   3  Local variable is assigned to but never used
# E302   2  Expected 2 blank lines after class or function definition
# N802   2  Function name should be lowercase
# 
# Total: 32 violations across 6 rules

Без --statistics Ruff выводит каждое нарушение отдельно. С этим флагом — краткую сводку. Комбинировать с другими флагами:

# статистика только по выбранным правилам
ruff check --select E --statistics .

# статистика с игнорированием
ruff check --ignore E501 --statistics .

# можно сохранить вывод в файл
ruff check --statistics . > lint_stats.txt

Пример использования в процессе миграции:

  1. Запустить ruff check --statistics . на проекте
  2. Посмотреть топ нарушений
  3. Добавить самые частые в ignore временно
  4. Постепенно исправлять и убирать из ignore
📁

--show-files — какие файлы проверяются

Флаг --show-files выводит список файлов, которые Ruff будет проверять с учётом всех настроек включения/исключения (include, exclude, extend-exclude). Это помогает отладить конфигурацию, когда вы не уверены, почему какой-то файл проверяется или не проверяется.

ruff check --show-files .
# src/main.py
# src/utils.py
# src/models.py
# tests/test_main.py

Полезно, когда у вас настроены exclude-правила:

# pyproject.toml
[tool.ruff]
exclude = ["migrations/", "build/", "*.generated.py"]

# CLI — посмотреть что осталось
ruff check --show-files .

Вывод --show-files учитывает все настройки: src (корневая директория), include (какие файлы включать по расширению) и exclude.

⚖️

CLI vs конфигурационный файл: приоритет

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

Настройка CLI флаг Приоритет
select --select CLI полностью заменяет конфиг
extend-select --extend-select CLI дополняет конфиг (оба активны)
ignore --ignore CLI добавляется к ignore из конфига
fix --fix CLI включает, независимо от конфига
line-length Нет CLI флага Только конфиг
exclude --exclude CLI заменяет exclude из конфига

Важно: --select из CLI полностью переопределяет select из конфига. Если в конфиге указано select = ["E", "F"], а вы запускаете ruff check --select B ., то будут проверяться только правила B. Именно поэтому существует --extend-select, который не заменяет, а дополняет.

⚠️ Если вы используете --select в CLI, убедитесь, что это действительно то, что вам нужно. Частая ошибка — случайная перезапись всех настроек конфига.
🛠️

Практические сценарии

Сценарий 1: Быстрая проверка нового кода

# проверить только новые изменения (через git)
ruff check --select ALL $(git diff --name-only --diff-filter=AM)
# или просто проверить файлы в staged
ruff check --select E,F,I $(git diff --cached --name-only)

Сценарий 2: CI с поэтапным внедрением

# Этап 1: только критические ошибки
ruff check --select F --statistics .

# Этап 2: добавить стиль
ruff check --select F,E --statistics .

# Этап 3: полная проверка с исключениями
ruff check --select ALL --ignore E501,D --statistics .

Сценарий 3: Поиск конкретных проблем

# найти все проблемы безопасности
ruff check --select S .

# найти все проблемы с производительностью (flake8-perflint)
ruff check --select PERF .

# найти все, что связано с logging
ruff check --select LOG .
🔧 Совет: создайте alias в shell для быстрого запуска: alias ruffci="ruff check --select ALL --ignore E501,D ."
🔇

Подавление предупреждений: noqa

Иногда нужно подавить предупреждение для конкретной строки. Для этого используется комментарий # noqa:

import os  # noqa: F401  — подавить только правило F401
import sys  # noqa  — подавить все правила на этой строке

x = 1 + 2  # noqa: E225

# Можно указать несколько правил через запятую
long_variable_name = "very long string that exceeds line length"  # noqa: E501, W291

# Подавить на уровне файла — в начале файла
# ruff: noqa: F401, E501

Можно также настроить noqa в конфиге — например, требовать обязательного указания кода правила (чтобы не было голых # noqa):

# pyproject.toml
[tool.ruff.lint]
select = ["E", "F", "I"]

[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]  # во всех __init__.py игнорировать F401
"tests/*.py" = ["E501"]    # в тестах игнорировать длину строк
💡 Старайтесь всегда указывать конкретное правило в # noqa: XXX, а не просто # noqa. Это документирует, почему подавлено предупреждение, и не скрывает новые ошибки.
📄

per-file-ignores — игнорирование по файлам

Настройка per-file-ignores позволяет игнорировать определённые правила для конкретных файлов или групп файлов (по glob-паттерну). Это удобно, когда определённые типы файлов имеют свои особенности:

# pyproject.toml
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401", "F403"]   # в __init__.py часто "from . import *"
"tests/**/*.py" = ["E501", "S101"] # в тестах — длинные строки и assert
"migrations/**/*.py" = ["ALL"]     # миграции — вообще не проверять
"examples/*.py" = ["E501"]         # примеры могут иметь длинные docstring
"scripts/**/*.py" = ["N802"]       # в скриптах могут быть функции без snake_case

Эта настройка работает через конфигурационный файл, но её нельзя передать через CLI напрямую.

📋

Резюме

Флаг Назначение
--select Выбрать только указанные правила (заменяет конфиг)
--extend-select Добавить правила к существующему набору
--ignore Исключить указанные правила
--statistics Показать статистику нарушений
--show-files Показать какие файлы будут проверяться
# noqa Подавить предупреждение в конкретной строке
per-file-ignores Игнорировать правила для групп файлов
📖 Документация: docs.astral.sh/ruff/linter/
📝

Шпаргалка: популярные комбинации

# Минимальная проверка (по умолчанию)
ruff check .

# Стандартный набор для нового проекта
ruff check --select E,F,I,N,UP,B .

# Полная проверка безопасности
ruff check --select S .

# Для CI/CD — только критические ошибки
ruff check --select F --statistics .

# Для существующего проекта — без длины строк
ruff check --ignore E501 .

# Для Jupyter Notebooks
ruff check notebook.ipynb .

# Только сортировка импортов
ruff check --select I --fix .

# Проверка с автоприменением безопасных исправлений
ruff check --fix --select E,F,I .

# Статистика по всем правилам
ruff check --select ALL --statistics . 2>/dev/null

Урок 2.1: ruff check — флаги и опции

5 вопросов