$ sudo teach IT
Модуль 5 · Правила

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

E, F, I, N, UP, B, S и другие — что за каждой буквой.

📖 Теория 🕓 ~25 минут
📚

Что такое категории правил

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

Всего в Ruff более 800 правил, объединённых в ~40 категорий. Категории (префиксы) используются в параметрах select, ignore, fixable и других, как вы уже знаете из урока 4.2.

💡 Как устроен код правила: Каждое правило имеет код из букв и цифр, например F401. Первая часть — префикс категории (F), вторая — номер внутри категории (401). Некоторые категории имеют длинные префиксы: C90, UP, ANN, PL.
📃

Полный список категорий правил

Префикс Название Описание Пример
E pycodestyle errors Нарушения PEP 8: пробелы, отступы, пустые строки, длина строки E501 line too long
W pycodestyle warnings Предупреждения о стиле: trailing whitespace, лишние пустые строки W291 trailing whitespace
F Pyflakes Логические ошибки: неиспользуемые импорты/переменные, неопределённые имена F401 unused import
I isort Порядок и группировка импортов по PEP 8 I001 unsorted imports
N pep8-naming Именование: классы CamelCase, функции snake_case, константы UPPER_CASE N801 class name
UP pyupgrade Обновление синтаксиса до современных версий Python UP006 typing → builtins
B flake8-bugbear Распространённые баги: mutable default args, голые except, assert False B006 mutable default
S flake8-bandit Безопасность: SQL-инъекции, небезопасные вызовы, hardcoded пароли S105 hardcoded password
C90 mccabe Цикломатическая сложность — находит слишком сложные функции C901 too complex
ANN flake8-annotations Аннотации типов у функций и методов ANN001 missing type arg
ARG flake8-unused-arguments Неиспользуемые аргументы функций ARG001 unused argument
D pydocstyle Проверка docstring'ов: наличие, формат, соответствие PEP 257 D100 missing docstring
RUF Ruff-specific Уникальные правила Ruff без аналогов в других линтерах RUF100 unused noqa
PL Pylint Реализация правил Pylint (около 200 правил) PLR0911 too many returns
SIM flake8-simplify Упрощение кода: замена сложных конструкций на простые SIM101 duplicate isinstance
T20 flake8-print Запрет на print() в production-коде T201 print statement
PT flake8-pytest-style Лучшие практики pytest: правильные assert, именование тестов PT001 pytest fixture
RET flake8-return Проверка корректности return в функциях RET501 explicit return None
SLF flake8-self Запрет на обращение к приватным членам (self.__x) SLF001 private member access
ERA eradicate Находит закомментированный код (dead code) ERA001 commented code
PD pandas-vet Проверка pandas кода: inplace, chained indexing PD002 inplace=True
NPY NumPy-specific Правила для работы с NumPy NPY001 numpy deprecation
FA flake8-future-annotations Проверка from __future__ import annotations FA100 missing future import
ISC flake8-implicit-str-concat Проверка неявной конкатенации строк ISC001 implicit concat
IC flake8-import-conventions Стандартные соглашения по импортам (например, import numpy as np) IC001 import convention
PTH flake8-use-pathlib Использовать pathlib вместо os.path PTH101 use pathlib
TD flake8-todos Проверка TODO/FIXME комментариев TD001 TODO format
DJ flake8-django Лучшие практики Django: модели, views, urls DJ001 model null field
ASYNC flake8-async Правила для асинхронного кода ASYNC100 async function
TCH flake8-type-checking Правила для TYPE_CHECKING блоков TCH001 type check import
PYI flake8-pyi Правила для stub-файлов (.pyi) PYI001 stub syntax
EXE flake8-executable Проверка shebang и прав на выполнение EXE001 shebang
RSE flake8-raise Правила для raise: избегать дублирования RSE001 raise from
FURB refurb Модернизация и упрощение кода (от проекта Refurb) FURB101 simplify
COM flake8-commas Проверка запятых в кортежах/списках/словарях COM812 trailing comma
FLY flynt Замена % и .format() на f-strings FLY001 use f-string
PERF perflint Проверки производительности Python PERF101 manual loop
DOC pydoclint Проверка соответствия docstring'ов сигнатурам функций DOC001 docstring mismatch
DTZ flake8-datetimez Проверка timezone-aware datetime DTZ001 naive datetime
EM flake8-errmsg Проверка сообщений об ошибках EM101 error message
G flake8-logging-format Проверка форматирования логов G001 logging format
LOG flake8-logging Правила для logging: уровень, lazy formatting LOG001 logging call
INP flake8-ini Проверка .ini файлов INP001 ini file
PIE flake8-pie Разные полезные проверки (prefer fstrings, prefer is over ==) PIE781 prefer is None
Q flake8-quotes Проверка стиля кавычек (одинарные/двойные) Q000 bad quotes
R ruff-format Встроенные правила форматирования Ruff R001 format
📌

E — pycodestyle errors (PEP 8)

Категория E — это ядро стилистических проверок. Они следят за соблюдением PEP 8 — официального руководства по стилю кода Python. Эти правила включены по умолчанию вместе с категорией F.

Что проверяет категория E:

  • E101-E111 — отступы (пробелы vs табы)
  • E201-E262 — пробелы вокруг операторов и скобок
  • E301-E306 — количество пустых строк
  • E401-E501 — импорты, длина строки
  • E701-E742 — составные statements
  • W — предупреждения (trailing whitespace, лишние пустые строки)

Самые частые правила:

# E501: line too long (> 88 символов по умолчанию)
x = "very long string that exceeds the maximum line length configured in ruff"  # E501

# E302: expected 2 blank lines before class/function definition
class MyClass:
    pass

# E225: missing whitespace around operator
x=1  # должно быть x = 1

# E111: indentation is not a multiple of 4
  print("wrong indent")  # 2 spaces вместо 4

# W291: trailing whitespace
print("hello")   # пробел в конце строки
💡 Совет: Используйте Ruff Formatter для автоматического исправления большинства проблем категории E. Ruff форматирует код по PEP 8, устраняя ошибки отступов, пробелов и пустых строк.
🔎

F — Pyflakes (логические ошибки)

Категория F (Pyflakes) — одна из самых важных. Она находит реальные логические ошибки в коде, которые могут привести к багам. Pyflakes известен своей скоростью и точностью — он не выполняет код, но находит проблемы статически.

# F401: imported but unused
import os  # F401 — os не используется

# F841: local variable assigned but never used
def foo():
    result = compute()  # F841 — result нигде не используется
    return "done"

# F811: redefinition of unused variable
def foo():
    x = 1
    x = 2  # F811 — x переопределён без использования

# F821: undefined name
print(undefined_var)  # F821 — имя не определено

# F601: dictionary key repeated
d = {"a": 1, "a": 2}  # F601 — ключ 'a' повторяется

# F403: from module import *
from math import *  # F403 — может конфликтовать с локальными именами

# F541: f-string without any placeholders
x = f"hello"  # F541 — f-string не нужен, можно обычную строку
💡 Совет: Правило F841 (unused variable) можно обойти, используя _ как имя переменной или явно вызывая del result. Ruff не ругается на односимвольные имена.
🔄

I — isort (импорты)

Категория I заменяет популярный инструмент isort. Она проверяет порядок и группировку импортов согласно PEP 8 и настраиваемым правилам.

Порядок импортов по умолчанию:

  1. Стандартная библиотека — os, sys, json, typing...
  2. Третьесторонние библиотеки — django, requests, numpy...
  3. Локальные модули — импорты из вашего проекта
# I001: Unsorted imports
# Плохо:
from myproject.models import User
import os
import django.db.models

# Хорошо:
import os
import django.db.models
from myproject.models import User

Настройка isort в Ruff:

[tool.ruff.lint]
# Включить сортировку импортов
extend-select = ["I"]

# Настройка порядка импортов
[tool.ruff.lint.isort]
known-third-party = ["django", "flask", "fastapi"]
known-first-party = ["myproject", "tests"]
required-imports = ["from __future__ import annotations"]
extra-standard-library = ["tomllib"]
💡 Совет: isort в Ruff автоматически исправляет порядок импортов при ruff check --fix. В VS Code можно настроить автоисправление при сохранении через source.organizeImports.ruff.
📝

N — pep8-naming (именование)

Категория N проверяет соответствие имён переменных, функций, классов и констант правилам PEP 8:

Правило Требование Пример ошибки
N801 Классы — CamelCase class my_class: → class MyClass:
N802 Функции — snake_case def MyFunction(): → def my_function():
N803 Аргументы — snake_case def foo(MyArg): → def foo(my_arg):
N804 Первый аргумент метода — cls (classmethod) def foo(self, x): над classmethod
N805 Первый аргумент метода — self def foo(cls, x): над обычным методом
N806 Константы — UPPER_CASE my_constant = 42 на уровне модуля
N807 Функции с __ — специальные (dunder) def __foo__(): не специальный метод
N815 mixedCase в глобальных переменных myGlobal = 1
N816 mixedCase в переменных класса self.myVariable = 1
# N801: класс должен быть CamelCase
class my_class:  # N801
    pass

# N802: функция должна быть snake_case
def MyFunction():  # N802
    pass

# N803: аргумент должен быть snake_case
def foo(MyArg):  # N803
    pass

# N806: константы — UPPER_CASE
my_constant = 42  # N806
🚀

UP — pyupgrade (синтаксис)

Категория UP (pyupgrade) предлагает заменить устаревшие конструкции на современные синтаксические возможности Python. Это помогает поддерживать код в актуальном состоянии и использовать новые возможности языка.

Категория UP зависит от target-version — минимальной версии Python, которую вы поддерживаете:

[tool.ruff]
target-version = "py310"   # минимальная версия — Python 3.10

Примеры правил UP:

# UP006: typing.List/Dict → list/dict (Python 3.9+)
from typing import List
x: List[str] = []      # UP006 → x: list[str] = []

# UP007: Optional/Union → | (Python 3.10+)
from typing import Optional
def foo(x: Optional[str]):  # UP007 → def foo(x: str | None):
    pass

# UP008: super() without arguments
class Child(Parent):
    def __init__(self):
        super(Child, self).__init__()  # UP008 → super().__init__()

# UP009: UTF-8 encoding declaration (не нужно в Python 3)
# -*- coding: utf-8 -*-  # UP009 — удалить

# UP012: f-strings with datetime
f"Today is {datetime.now()}"  # UP012 → datetime.now().isoformat()

# UP018: float() → литерал
x = float(1)  # UP018 → x = 1.0

# UP024: replace os.path with pathlib
import os
os.path.join("a", "b")  # UP024 → Path("a") / "b"

# UP025: удалить лишние скобки в кортежах
x = (1 + 2)  # UP025 → x = 1 + 2

# UP031: printf-style → f-string (Python 3.6+)
name = "World"
print("Hello, %s" % name)  # UP031 → f"Hello, {name}"

# UP034: лишние скобки с return
def foo():
    return (x)  # UP034 → return x
💡 Совет: Установите target-version в минимальную версию Python, которую вы поддерживаете. Ruff будет предлагать только те upgrade'ы, которые совместимы с этой версией. Например, если target-version = "py39", то UP007 (Union с |) не будет предлагаться.
🔥

B — flake8-bugbear (баги)

Категория B (Bugbear) находит распространённые баги и анти-паттерны в Python-коде. Это одна из самых полезных категорий для production-проектов.

# B006: mutable argument default
def append(item, lst=[]):  # B006 → lst=None
    lst.append(item)
    return lst

# B007: loop variable not used
for i in range(10):  # B007 → for _ in range(10):
    print("hello")

# B008: function call in default argument
def create_user(name, time=datetime.now()):  # B008
    pass

# B009: getattr with constant
value = getattr(obj, "name")  # B009 → obj.name

# B010: setattr with constant
setattr(obj, "name", value)  # B010 → obj.name = value

# B011: assert False
assert False, "error"  # B011 → raise AssertionError или raise ValueError

# B012: return inside finally
def foo():
    try:
        return 1
    finally:
        return 2  # B012 — предыдущий return потерян

# B013: except with tuple
except (ValueError, TypeError):  # B013 → except (ValueError, TypeError):
    pass

# B014: duplicate exception in except
except (ValueError, ValueError):  # B014

# B015: useless comparison
x == 1  # B015 — сравнение без использования

# B016: raise with exception instance
raise ValueError  # B016 → raise ... from ...
🛡

S — flake8-bandit (безопасность)

Категория S (Bandit) проверяет код на уязвимости безопасности: hardcoded пароли, SQL-инъекции, небезопасные вызовы и другие проблемы.

# S101: use of assert (может быть отключено в production)
assert user.is_admin  # S101

# S102: exec used
exec(code)  # S102 — exec опасен

# S103: eval used
eval("1+1")  # S103 — eval опасен

# S104: hardcoded binding to all interfaces
app.run(host="0.0.0.0")  # S104

# S105: hardcoded password
PASSWORD = "super-secret"  # S105

# S106: hardcoded password (named)
DB_PASSWORD = "password123"  # S106

# S107: hardcoded password in function
def login(password="admin123"):  # S107
    pass

# S108: hardcoded temporary directory
TEMP = "/tmp"  # S108

# S324: subprocess with shell=True
import subprocess
subprocess.run("ls", shell=True)  # S324 — shell injection risk

# S501: request without verification
import requests
requests.get("https://example.com", verify=False)  # S501

# S701: jinja2 with autoescape=False
import jinja2
jinja2.Environment(autoescape=False)  # S701
⚠️ Важно: Правила безопасности могут давать ложные срабатывания. Например, S105 на PASSWORD в переменных окружения (значение берётся из os.environ). Используйте per-file-ignores для конфигурационных файлов.
📜

PL — Pylint (реализация)

Ruff включает большую часть правил Pylint (~200 правил с префиксом PL). Pylint — один из самых старых и всесторонних линтеров для Python, но он известен своей медлительностью. Ruff реализует его правила со скоростью Ruff.

# PLR0911: too many return statements
def foo():
    if a: return 1
    if b: return 2
    if c: return 3  # PLR0911

# PLR0913: too many function arguments
def foo(a, b, c, d, e, f):  # PLR0913
    pass

# PLR0915: too many statements
def foo():
    # много строк...
    pass  # PLR0915

# PLC3001: unnecessary lambda
sorted(x, key=lambda a: a.name)  # → attrgetter

# PLR1704: redefined argument name
def foo(x):
    x = 1  # PLR1704

# PLW0120: useless else on loop
for x in items:
    if condition:
        break
else:  # PLW0120 — else не нужен, т.к. без break он всегда выполняется
    print("not found")
🤖

RUF — Ruff-specific (уникальные правила)

Категория RUF содержит правила, созданные специально для Ruff. У них нет аналогов в других инструментах. Эти правила решают проблемы, характерные именно для экосистемы Ruff, или предлагают уникальные проверки.

# RUF100: unused noqa directive
import os  # noqa: F401  # RUF100 — F401 не срабатывает, noqa не нужен

# RUF001: ambiguous unicode character
print("книга")  # RUF001 — если строка содержит кириллицу в ASCII контексте

# RUF002: ambiguous unicode character in docstring
def foo():
    """Описание с кириллицей."""  # RUF002

# RUF003: ambiguous unicode character in comment
# комментарий с кириллицей  # RUF003

# RUF005: use of collections.abc instead of typing
from typing import Iterable  # RUF005 → from collections.abc import Iterable

# RUF006: asyncio.sleep(0) → await asyncio.sleep(0)
import asyncio
asyncio.sleep(0)  # RUF006 → await asyncio.sleep(0)

# RUF010: use of explicit f-string conversion
f"{value!r}"  # RUF010 — можно repr(value)

# RUF012: mutable class attribute
class Foo:
    items = []  # RUF012 — mutable class attribute
📍

Дополнительные категории

SIM — flake8-simplify (упрощение)

# SIM101: duplicate isinstance
if isinstance(x, int) or isinstance(x, float):  # SIM101
    ...

# SIM102: nested if → single if with and
if a:
    if b:  # SIM102 → if a and b:
        pass

# SIM103: unnecessary else after return
def foo():
    if x:
        return True
    else:  # SIM103
        return False

# SIM108: if-else → ternary
if x:
    y = 1
else:  # SIM108 → y = 1 if x else 2
    y = 2

# SIM115: open → Path.open
file = open("file.txt")  # SIM115 → Path("file.txt").open()

ANN — flake8-annotations (типы)

# ANN001: missing type for function argument
def foo(x):  # ANN001 → def foo(x: int):
    pass

# ANN201: missing return type for public function
def foo():  # ANN201 → def foo() -> str:
    return "hello"

# ANN202: missing return type for private function
def _foo():  # ANN202 → def _foo() -> str:
    return "hello"

# ANN401: Any → avoid Any
from typing import Any
def foo(x: Any):  # ANN401 — лучше использовать конкретный тип
    pass
⚠️ Важно: ANN может быть очень строгим. В больших проектах начните с ANN201 (публичные функции) и ANN001 (аргументы). Добавляйте остальные правила постепенно.

T20 — flake8-print

# T201: print statement in production
print(f"User {user} not found")  # T201 — используй logging

PT — flake8-pytest-style

# PT001: pytest fixtures should use (scope="...")
@pytest.fixture
def user():  # PT001 → @pytest.fixture()
    pass

# PT009: unittest-style assert
assert something == True  # PT009 → assert something

# PT022: no teardown in fixture with yield
@pytest.fixture
def db():
    conn = connect()
    yield conn
    # PT022 — нет conn.close()

C90 — mccabe (цикломатическая сложность)

# C901: function is too complex (по умолчанию > 10)
def very_complex_function():  # C901
    if a:
        for b in c:
            while d:
                if e:
                    # ...
    # сложность > 10

Настройка порога сложности:

[tool.ruff.lint.mccabe]
max-complexity = 15  # по умолчанию 10

D — pydocstyle (документация)

# D100: missing docstring in public module
# D101: missing docstring in public class
# D102: missing docstring in public method
# D103: missing docstring in public function
# D104: missing docstring in public package
# D105: missing docstring in magic method
# D107: missing docstring in __init__

class Calculator:  # D101
    def add(self, x, y):  # D102
        return x + y

Ruff поддерживает все основные стили docstring:

  • Google-style
  • NumPy-style
  • Sphinx-style (reStructuredText)
  • PEP 257
[tool.ruff.lint.pydocstyle]
convention = "google"  # google, numpy, pep257 (по умолчанию)
🔍

Просмотр правил через CLI

Ruff предоставляет удобные CLI-команды для просмотра документации правил и категорий.

ruff rule — детали конкретного правила

ruff rule F401

# Вывод:
# F401: module-imported-but-unused (Pyflakes)
# A module is imported but not used.
# 
# Пример:
# import os  # F401
# 
# Автоисправление: Safe (удаляет неиспользуемый импорт)

Что показывает ruff rule:

  • Код и название правила
  • Категорию, к которой относится правило
  • Описание нарушения
  • Пример кода
  • Тип автоисправления (safe / unsafe / none)

ruff rule --all — все правила

ruff rule --all   # очень длинный список

# Фильтрация по категории через grep:
ruff rule --all | grep "^F"      # только Pyflakes
ruff rule --all | grep "^UP"     # только pyupgrade
ruff rule --all | grep "^PLR"    # только Pylint Refactoring
💡 Совет: Используйте ruff rule --all > rules.txt и просматривайте файл в редакторе. Это даст полную картину всех доступных проверок.

Пример: найти правило для вашей задачи

# Найти все правила, связанные с "except"
ruff rule --all | grep -i "except"

# Найти все правила с автоисправлением
ruff rule --all | grep "Safe fix"

# Найти правила для pandas
ruff rule --all | grep "^PD"

# Сколько всего правил в категории?
ruff rule --all | grep "^B" | wc -l
🔁

Рекомендуемые наборы категорий

Уровень Категории Для кого
Минимальный E, F Дефолт — стиль + логика
Базовый E, F, I, N Стиль + логика + импорты + именование
Средний E, F, I, N, UP, B + pyupgrade + bugbear
Продвинутый E, F, I, N, UP, B, S, C90 + безопасность + сложность
Строгий ALL Новые проекты, open-source библиотеки
Django E, F, I, N, UP, B, S, C90, DJ Django-проекты
FastAPI E, F, I, N, UP, B, ANN, ARG API-сервисы с типизацией
Data Science E, F, I, N, UP, B, PD, NPY, PERF pandas/NumPy + производительность
💡 Рекомендация: Для существующего проекта начните с Базового набора, исправьте все ошибки, затем добавляйте категории по одной. Для нового проекта сразу используйте Строгий набор с выборочными исключениями через ignore.
📝

Итоги

💡 Главные выводы:
  1. Ruff имеет более 800 правил в ~40 категориях.
  2. Категории включаются через префиксы: E, F, I, N, UP, B, S...
  3. E + F включены по умолчанию — это стиль и логика.
  4. I (isort) — обязательная категория для порядка в импортах.
  5. UP (pyupgrade) зависит от target-version.
  6. B (bugbear) находит реальные баги — очень рекомендуется.
  7. RUF — уникальные правила Ruff (RUF100 для noqa).
  8. Используйте ruff rule F401 для документации правила.
  9. Используйте ruff rule --all для полного списка.
  10. Начинайте с малого набора, добавляйте категории постепенно.

Урок 5.1: Категории правил

5 вопросов