per-file-ignores и noqa
Исключения для конкретных файлов и строк.
per-file-ignores — правила для конкретных файлов
Параметр per-file-ignores позволяет отключать определённые правила для файлов, соответствующих glob-паттерну. Это более гибкий аналог глобального ignore, действующий только для указанных файлов.
Синтаксис: ключ — glob-паттерн файлов, значение — список правил для отключения:
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]
"tests/**/*.py" = ["S101", "E501"]
"migrations/*.py" = ["ALL"]
"settings.py" = ["S105", "S106"]
Типичные сценарии использования
| Паттерн | Правила | Почему |
|---|---|---|
"__init__.py" |
["F401"] |
Реэкспорт в __init__.py — частая практика, импорт используется как public API |
"tests/**/*.py" |
["S101", "E501", "ANN"] |
В тестах assert обязателен, длинные строки нормальны, аннотации необязательны |
"*/migrations/*.py" |
["ALL"] |
Миграции генерируются автоматически, их правка только сломает Alembic |
"**/settings*.py" |
["S105", "S106"] |
В конфигах часто используются hardcoded значения для локальной разработки |
"**/conftest.py" |
["D"] |
Фикстуры в conftest редко документируются |
"scripts/**/*.py" |
["ANN", "D"] |
Одноразовые скрипты не требуют документации и аннотаций |
"**/docs/**/*.py" |
["D"] |
Примеры в документации — не основной код |
Glob-паттерны: подробности
Ruff использует glob-паттерны для сопоставления файлов. Вот как они работают:
| Паттерн | Совпадение |
|---|---|
"__init__.py" |
Только файл __init__.py в корне проекта |
"src/__init__.py" |
Только src/__init__.py |
"tests/**/*.py" |
Любой .py файл в tests/ и всех подпапках |
"**/migrations/*.py" |
Любой .py файл в папке migrations на любом уровне вложенности |
"*.py" |
Все .py файлы в корне (не рекурсивно) |
"__init__.py" без пути, паттерн сработает только для файла в корне проекта. Чтобы охватить все __init__.py, используйте "**/__init__.py".
Полный пример конфигурации с per-file-ignores
[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "B", "S", "ANN"]
ignore = ["E501"]
[tool.ruff.lint.per-file-ignores]
# в тестах разрешены assert и длинные строки
"tests/**/*.py" = ["S101", "ANN", "D"]
# в миграциях отключено всё — они автосгенерированы
"*/migrations/*.py" = ["ALL"]
# в __init__.py разрешён реэкспорт
"**/__init__.py" = ["F401"]
# в conftest не нужны аннотации
"**/conftest.py" = ["ANN"]
# в конфигах можно hardcoded значения
"**/settings*.py" = ["S105", "S106"]
# в скриптах не обязательны аннотации
"scripts/**/*.py" = ["ANN", "D"]
# в примерах документации не нужна документрация
"**/docs/**/examples/**/*.py" = ["D", "ANN"]
Синтаксис в разных конфигах
pyproject.toml
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]
"tests/**/*.py" = ["S101", "E501"]
ruff.toml
[lint.per-file-ignores]
"__init__.py" = ["F401"]
"tests/**/*.py" = ["S101", "E501"]
tool.ruff.lint, в ruff.toml достаточно lint. Это стандартная разница между TOML-конфигом и PEP 621.
noqa — отключить для строки
Комментарий # noqa на строке кода отключает проверки Ruff для этой строки. Это самый точный способ подавления предупреждений — он действует только на одну строку и не затрагивает остальной код.
Форматы noqa
| Формат | Пример | Что отключает |
|---|---|---|
| Без кода | # noqa |
Все правила для этой строки |
| Одно правило | # noqa: F401 |
Только правило F401 для этой строки |
| Несколько правил | # noqa: F401, E302 |
Правила F401 и E302 для этой строки |
import os # noqa: F401 # отключить одно правило
import sys # noqa: F401, E302 # несколько правил
import json # noqa # отключить все правила для строки
x = 1 # noqa: E501 # длинная строка, но тут коротко
class MyClass: # noqa: N801 # разрешить имя не в CamelCase
pass
# noqa без кода правила — плохая практика. Всегда указывайте конкретный код, чтобы было понятно, что именно отключено и почему. Это облегчает код-ревью и будущее обслуживание.
Правильный способ использования noqa
Хорошая практика — комментировать причину отключения:
# Хорошо: указан код и причина
import os # noqa: F401 — используется в type: ignore
# Плохо: без кода — непонятно что отключено
import os # noqa
# Плохо: без причины — придётся гадать на код-ревью
import os # noqa: F401
# ruff: noqa — отключить для всего файла
Специальный комментарий # ruff: noqa в первой строке файла (или сразу после shebang) отключает проверки для всего файла. Это удобнее и чище, чем per-file-ignores для отдельных случаев.
# ruff: noqa # отключить все правила в файле
# ruff: noqa: E501 # отключить E501 во всём файле
# ruff: noqa: E501, F401 # несколько правил для всего файла
# ruff: noqa: E501, F401, N801 # комбинированный
Когда использовать # ruff: noqa вместо per-file-ignores:
| Сценарий | Что лучше | Почему |
|---|---|---|
| Один файл с исключением | # ruff: noqa |
Локальное решение, не требует правки конфига |
| Много файлов по паттерну | per-file-ignores | Одна запись в конфиге вместо кучи комментариев |
| Временное отключение | # ruff: noqa |
Легко найти и убрать позже |
# ruff: noqa должен быть в самой первой строке файла. Если первой строкой идёт shebang (#!/usr/bin/env python), то # ruff: noqa должен быть второй строкой.
isort action comments
Ruff поддерживает комментарии isort для управления группировкой импортов. Это позволяет контролировать, как Ruff (в режиме isort) обрабатывает импорты в конкретных файлах.
| Комментарий | Описание |
|---|---|
# isort: skip_file |
Пропустить весь файл при сортировке импортов |
# isort: skip |
Пропустить следующую строку импорта |
# isort: split |
Принудительно разделить группы импортов |
# isort: dont-add-to-section |
Не добавлять импорт в секцию |
# isort: skip_file
import os
import sys
# Этот файл не будет сортироваться isort
import os
import sys
# isort: split — принудительное разделение
import django # эта группа не смешается с stdlib
import requests
# isort: skip — пропустить один импорт
import os
import sys # isort: skip # этот импорт не будет переставлен
import json
# isort: split полезен, когда нужно явно отделить, например, внутренние импорты от библиотечных, если Ruff неверно определяет их тип.
RUF100 — обнаружение лишних noqa
Правило RUF100 (Ruff-specific) находит # noqa комментарии, которые стали не нужны — то есть правила, которые они отключают, больше не срабатывают на этой строке. Это помогает поддерживать код в чистоте и не накапливать мёртвые подавления.
Как это работает
Когда правило перестаёт срабатывать на строке (например, вы исправили код, обновили конфиг или правило было удалено), # noqa для этого правила становится избыточным. RUF100 сообщит об этом:
import os # noqa: F401
# Если F401 больше не активен (правило отключено в конфиге),
# Ruff выдаст: RUF100 Unused noqa directive 'F401'
Автоматическое удаление лишних noqa
Ruff может автоматически удалять ненужные noqa-комментарии:
# Запустить проверку + автоудаление лишних noqa
ruff check --fix --unsafe-fixes
# Только RUF100
ruff check --select RUF100 --fix
--unsafe-fixes для автоисправления RUF100.
Типичные причины появления лишних noqa
- Правило было отключено в конфиге (через ignore), но noqa остался.
- Правило было удалено из select, но noqa остался.
- Код был изменён и больше не нарушает правило.
- Обновление Ruff — правило перестало существовать или было переименовано.
# Пример: правило отключено через ignore
# [tool.ruff.lint]
# ignore = ["F401"]
import os # noqa: F401 # RUF100: это больше не нужно — F401 и так отключён
--add-noqa — автоматическое добавление noqa
Флаг --add-noqa автоматически добавляет # noqa: {rule} ко всем строкам, где Ruff находит нарушения. Это полезно, когда вы внедряете Ruff в существующий проект и хотите сначала заглушить все ошибки, а потом исправлять их постепенно.
# Добавить noqa ко всем текущим нарушениям
ruff check --add-noqa
# Добавить noqa только для конкретного правила
ruff check --add-noqa --select F401
# Добавить noqa для конкретной директории
ruff check src/ --add-noqa
После добавления noqa, код будет чист с точки зрения Ruff, и вы сможете постепенно удалять noqa по мере исправления кода:
# До --add-noqa:
import os
import sys
# После --add-noqa:
import os # noqa: F401
import sys # noqa: F401
--add-noqa добавляет noqa только для правил, которые активны в вашей конфигурации на момент запуска. Если вы потом добавите новые правила, старые noqa могут стать неполными.
Стратегия внедрения Ruff в legacy проект
- Установите Ruff и настройте
selectминимально (например, E, F, I). - Запустите
ruff check --add-noqa— все текущие ошибки будут заглушены noqa. - Настройте pre-commit с Ruff.
- Теперь Ruff будет проверять только новый код (старые ошибки заглушены).
- Постепенно исправляйте старые noqa (RUF100 подсветит неактуальные).
- Добавляйте новые категории правил по одной.
# Шаг 1: минимальная конфигурация
[tool.ruff.lint]
extend-select = ["E", "F", "I"]
# Шаг 2: заглушить все ошибки
# ruff check --add-noqa
# Шаг 3: починить код и удалить noqa
# ruff check --fix
# Шаг 4: добавить больше правил
[tool.ruff.lint]
extend-select = ["E", "F", "I", "N", "UP"]
# Шаг 5: снова заглушить новые ошибки и повторять
Блоковые noqa (не поддерживается)
# noqa: disable / # noqa: enable). Используйте # ruff: noqa для файла целиком или # noqa построчно.
Если вам нужно отключить проверку для нескольких строк подряд без # noqa на каждой строке, есть два варианта:
- Добавить
# ruff: noqaв начало файла — отключит всё в файле. - Вынести проблемный код в отдельный файл и настроить per-file-ignores.
Приоритет подавления правил
Когда вы используете несколько механизмов подавления, работает следующий приоритет (от высшего к низшему):
- # noqa на конкретной строке — имеет наивысший приоритет.
- # ruff: noqa в начале файла — отключает для всего файла.
- per-file-ignores — настройка в конфиге для паттерна файлов.
- ignore — глобальное отключение правил.
- select / extend-select — какие правила вообще включены.
Это означает, что:
# noqa: F401переопределитselect = ["F401"](правило включено, но noqa отключает).per-file-ignoresпереопределит глобальныйselectдля конкретных файлов.- Если правило не входит в
select, никакой noqa для него не нужен.
Сводная таблица методов подавления
| Метод | Уровень | Приоритет | Пример |
|---|---|---|---|
| noqa строковый | Строка | Высший | import os # noqa: F401 |
| ruff: noqa файловый | Файл | Высокий | # ruff: noqa: E501 |
| per-file-ignores | Паттерн файлов | Средний | "tests/*.py" = ["S101"] |
| ignore | Глобальный | Низкий | ignore = ["E501"] |
| isort action | Файл/строка | Средний | # isort: skip |
Частые ошибки и их решения
# noqa:F401 (без пробела), но Ruff не игнорирует правило.
# noqa:. Правильный формат: # noqa: F401.
# ruff: noqa: F401 в середине файла, но это не работает.
# ruff: noqa работает только в первой строке файла (или второй, если первая — shebang). В середине файла этот комментарий игнорируется.
"__init__.py" (без пути) совпадает только с файлом в корне. Используйте "**/__init__.py" для всех вложенных.
--add-noqa, но noqa не добавились.
select) и действительно находят нарушения. Попробуйте сначала ruff check . без --add-noqa — если нет нарушений, то и добавлять нечего.
ruff check --select RUF100 — он покажет все неиспользуемые noqa. Затем ruff check --fix --unsafe-fixes --select RUF100 — удалит их.
Практические примеры
Пример 1: Django проект
[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "B", "S", "DJ"]
[tool.ruff.lint.per-file-ignores]
"*/migrations/*.py" = ["ALL"]
"*/tests/**/*.py" = ["S101", "DJ"]
"**/settings.py" = ["S105", "S106"]
"**/admin.py" = ["F401", "D"]
"**/models.py" = ["D"]
Пример 2: FastAPI проект
[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "B", "ANN"]
[tool.ruff.lint.per-file-ignores]
"**/schemas.py" = ["B008"] # Pydantic использует Field(...) в дефолтах
"**/dependencies.py" = ["B008"] # FastAPI зависимости
"**/routers/**/*.py" = ["ANN"] # В роутерах аннотации необязательны
"tests/**/*.py" = ["ANN", "S101"]
"**/config.py" = ["S105"]
Пример 3: Пакет с __init__.py реэкспортами
# my_package/__init__.py
from my_package.core import MyClass # noqa: F401 — реэкспорт для внешнего API
from my_package.utils import helper # noqa: F401
__all__ = ["MyClass", "helper"]
Или через per-file-ignores:
[tool.ruff.lint.per-file-ignores]
"**/__init__.py" = ["F401"]
Пример 4: Проект с Jupyter ноутбуками
[tool.ruff.lint]
select = ["E", "F", "I"]
[tool.ruff.lint.per-file-ignores]
"**/notebooks/**/*.py" = ["ALL"] # экспортированные ноутбуки
"**/experiments/**/*.py" = ["ALL"] # экспериментальный код
Итоги
- per-file-ignores — для целых категорий файлов (тесты, миграции, __init__.py).
- # noqa — для конкретных строк, всегда указывайте код правила.
- # ruff: noqa — для целого файла (в первой строке).
- RUF100 находит устаревшие noqa — регулярно чистите их.
- --add-noqa помогает быстро заглушить ошибки в legacy-коде.
- isort action comments управляют сортировкой импортов.
- Блоковые noqa (
# noqa: disable) в Ruff не поддерживаются.
Урок 4.3: per-file-ignores и noqa
5 вопросов