1. Что такое код линтера
Когда линтер анализирует Python-код, он может сообщить не просто:
здесь проблема
а, например:
F401 'os' imported but unused
или:
E501 line too long
Буквенно-цифровая комбинация F401, E501 и т. п. называется кодом правила (rule code), кодом нарушения (violation code) или диагностическим кодом.
Например:
import os
если os нигде не используется, может привести к:
F401 'os' imported but unused
Код F401 позволяет однозначно понять, какое именно правило сработало.
2. Кто вообще выдаёт эти коды
Важно понимать: Python сам по себе не имеет единого набора кодов линтера.
Коды принадлежат конкретным инструментам или их наборам правил.
Например:
| Код | Источник | Назначение |
|---|---|---|
F401 | Pyflakes | неиспользуемый импорт |
E501 | pycodestyle | слишком длинная строка |
E302 | pycodestyle | неправильное количество пустых строк |
B008 | flake8-bugbear | потенциально проблемный вызов функции в аргументе по умолчанию |
UP006 | pyupgrade | устаревший синтаксис аннотации типа |
I001 | isort | неправильный порядок импортов |
D401 | pydocstyle | проблема с первой строкой docstring |
ANN001 | flake8-annotations | отсутствует аннотация аргумента |
Современный RUFF объединил поддержку огромного количества таких правил в одном инструменте.
Ruff использует систему кодов, исторически связанную с Flake8 и его плагинами. Например, F означает Pyflakes, а E — pycodestyle.
3. Как читать код
Большинство классических кодов выглядят примерно так:
F401
E501
B008
UP006
I001
D401
Условно:
┌───┐
│ F │ ← семейство / источник правил
└───┘
401 ← конкретное правило
Но это не универсальная математическая система.
Например:
F401
не означает:
F = ошибка, 401 = номер ошибки.
Правильнее понимать:
F401— конкретное правило из семействаF.
В Ruff префикс является частью идентификатора правила. Ruff также позволяет выбирать сразу целое семейство:
[tool.ruff.lint]
select = ['F']
Это означает: включить правила семейства F.
4. Семейство F — Pyflakes
Это одно из самых важных семейств.
F = Pyflakes
Оно в первую очередь ищет логические проблемы в Python-коде, связанные с импортами, именами и переменными.
F401 — неиспользуемый импорт
import os
если os нигде не используется:
F401 'os' imported but unused
То есть:
F401
↓
неиспользуемый импорт
Ruff может автоматически удалить такой импорт.
F403 — import *
Например:
from module import *
Ruff/Pyflakes сообщает:
F403 'from module import *' used; unable to detect undefined names
Проблема заключается в том, что анализатор не может заранее точно определить, какие имена попадут в namespace.
Поэтому:
from module import *
обычно считается плохой практикой.
F405 — использование имени после import *
Например:
from module import *
print(something)
Линтер может сообщить:
F405 something may be undefined, or defined from star imports
Причина опять связана с неопределённостью import *.
F821 — неизвестное имя
Например:
print(user_name)
при отсутствии определения:
F821 undefined name 'user_name'
Это уже гораздо более серьёзный сигнал.
Например:
def test():
print(result)
Если result нигде не существует, вероятно, это настоящая ошибка.
F841 — присвоенная, но неиспользуемая локальная переменная
def calculate():
result = 100
return 50
result не используется:
F841 local variable 'result' is assigned to but never used
5. Семейство E — pycodestyle
E = pycodestyle
Это уже в большей степени стиль и структура исходного кода.
Например:
E401
E402
E501
E701
E401 — несколько импортов в одной строке
import os, sys
Вместо этого:
import os
import sys
E402 — импорт находится не в начале файла
Например:
print('hello')
import os
Линтер может выдать:
E402 module level import not at top of file
Для обычного Python-модуля импорты обычно располагаются в начале файла.
E501 — слишком длинная строка
Например:
message = 'Очень длинная строка, которая превышает установленный максимальный размер строки'
Результат:
E501 line too long
В Ruff лимит определяется настройкой line-length; по умолчанию Ruff использует 88 символов.
Например:
[tool.ruff]
line-length = 100
Теперь 100 символов являются установленным пределом.
6. Почему E501 и форматтер — не одно и то же
Это важное различие.
Линтер спрашивает:
Нарушает ли код правило?
Форматтер спрашивает:
Как привести код к единому виду?
Например:
result=foo(a,b,c)
Форматтер может автоматически сделать:
result = foo(a, b, c)
Поэтому современные проекты часто используют:
Ruff
├── lint
└── format
а не десяток отдельных инструментов.
7. B — flake8-bugbear
Семейство:
B
обычно связано с потенциальными ошибками и сомнительными конструкциями, а не просто с форматированием.
Например:
B008
может указывать на вызов функции в аргументе со значением по умолчанию.
Типичный пример:
def get_data(value=get_config()):
...
Здесь get_config() вызывается при определении функции, а не при каждом вызове.
Это может быть неожиданным поведением.
8. UP — pyupgrade
Семейство:
UP
означает правила pyupgrade.
Их задача — находить конструкции, которые можно заменить на более современный Python-синтаксис.
Например, старый стиль:
from typing import List
values: List[str]
для современных версий Python может быть заменён:
values: list[str]
Поэтому:
UP006
может указывать на использование старого синтаксиса аннотаций.
9. I — isort
I
Связан с сортировкой импортов.
Например:
import sys
from pathlib import Path
import os
Инструмент может предложить привести импорты к согласованному порядку.
Например:
import os
import sys
from pathlib import Path
Типичное правило:
I001
означает проблему с организацией импортов.
10. D — pydocstyle
D
Это семейство правил для docstring.
Особенно интересно для проектов, где документация является частью архитектуры.
Например:
D400
D401
D417
D400
Проверяет окончание первой строки docstring.
Например:
def calculate():
"""Calculates result"""
Может потребоваться:
def calculate():
"""Calculates result."""
D401
Проверяет стиль первой строки docstring.
D417
Проверяет документирование параметров.
Ruff поддерживает эти правила, в том числе правила pydocstyle.
11. ANN — аннотации типов
Например:
ANN001
ANN201
ANN202
Это правила, связанные с проверкой type annotations.
Например:
def calculate(value):
return value * 2
Линтер может требовать:
def calculate(value: int) -> int:
return value * 2
Это особенно полезно для больших проектов.
12. RUF — собственные правила Ruff
У Ruff есть собственное семейство:
RUF
Например:
RUF001
RUF002
RUF003
Это правила, специфичные для Ruff.
Поэтому важно понимать:
F401
и
RUF001
имеют разное происхождение.
Ruff сейчас содержит более 900 правил, многие из которых происходят из популярных инструментов Python-экосистемы, но реализованы непосредственно в Ruff.
13. Самая важная вещь: код не обязательно означает «ошибка»
Слово error здесь может вводить в заблуждение.
Например:
F401
может быть:
импорт не используется
Это не обязательно означает, что программа не запустится.
А:
F821
уже потенциально указывает на реальную ошибку:
print(unknown_variable)
Поэтому полезно мысленно разделять:
┌─────────────────────────────┐
│ Диагностика │
├─────────────────────────────┤
│ Ошибка корректности │
│ Стиль │
│ Архитектурное предупреждение│
│ Безопасность │
│ Типизация │
│ Документация │
│ Производительность │
└─────────────────────────────┘
14. Что такое noqa
Теперь становится понятным:
from module import * # noqa: F403,F401
noqa означает:
не сообщать о выбранных нарушениях на этой строке.
То есть:
F403 → игнорировать
F401 → игнорировать
Но остальные правила продолжают работать.
Например:
from module import * # noqa: F403,F401
Это отличается от:
from module import * # noqa
Во втором случае подавляются все подходящие диагностики этой строки.
Ruff поддерживает noqa в стиле Flake8 и позволяет указывать конкретные коды правил.
15. Где ещё можно отключать правила
Не обязательно писать noqa возле каждой строки.
Можно сделать это глобально.
Например:
[tool.ruff.lint]
select = ['E', 'F']
ignore = ['E501']
Получается:
E → включено
F → включено
E501 → исключено
Можно также включить конкретное правило:
[tool.ruff.lint]
select = ['E', 'F', 'B']
или расширить существующий набор:
[tool.ruff.lint]
extend-select = ['B']
Ruff поддерживает также per-file-ignores, когда правило отключается только для определённых файлов.
16. Почему # noqa нельзя использовать повсюду
Плохая практика:
import os # noqa
import sys # noqa
import json # noqa
Таким образом можно фактически выключить линтер.
Гораздо лучше:
import os # noqa: F401
если действительно известно, почему импорт должен остаться.
Ещё лучше — устранить причину:
import os
→ удалить импорт, если он не нужен.
17. Особый случай: init.py
Вот здесь F401 часто бывает намеренным.
Например:
# package/__init__.py
from .client import Client
from .server import Server
Client и Server могут не использоваться внутри __init__.py.
Но они экспортируются пользователю:
from package import Client
Поэтому удаление импорта было бы неправильным.
В таком случае можно использовать:
from .client import Client # noqa: F401
from .server import Server # noqa: F401
То есть:
импорт выглядит неиспользуемым для линтера, но используется как часть публичного API пакета.
Это хороший пример того, почему автоматическое исправление не всегда означает правильное исправление.
18. Как узнать, что означает конкретный код
Если установлен Ruff:
ruff rule F401
Можно получить описание правила.
Например:
ruff rule E501
или:
ruff rule B008
А список правил:
ruff rule --all
В документации Ruff также есть полный каталог правил.
19. Как посмотреть, почему правило сработало
Например:
ruff check .
Получаем:
src\main.py:12:1: F401 `os` imported but unused
Здесь:
src\main.py
↓
строка 12
↓
позиция 1
↓
F401
↓
описание проблемы
Можно попросить Ruff автоматически исправить то, что он считает безопасным:
ruff check . --fix
Но после автоматических изменений код всё равно желательно проверить.
20. Почему один и тот же код можно встретить у разных инструментов
Здесь возникает важный исторический момент.
Flake8 — это не один анализатор.
В его экосистеме используются разные компоненты:
Flake8
├── pycodestyle
├── Pyflakes
├── mccabe
└── plugins
Поэтому:
F401
пришёл из Pyflakes,
а:
E501
из pycodestyle.
Flake8 документирует эти коды как error/violation codes и позволяет плагинам добавлять собственные семейства.
Ruff пошёл другим путём:
Ruff
│
┌────────┼─────────┐
│ │ │
F/E B UP
│ │ │
Pyflakes bugbear pyupgrade
При этом Ruff не запускает обязательно оригинальные Python-пакеты — он реализует эти правила самостоятельно.
21. Полезная карта кодов
Для повседневной разработки достаточно сначала запомнить следующие:
| Код | Источник / семейство | Что обычно означает |
|---|---|---|
F401 | Pyflakes | неиспользуемый импорт |
F403 | Pyflakes | import * |
F405 | Pyflakes | возможное неопределённое имя из import * |
F821 | Pyflakes | неопределённое имя |
F841 | Pyflakes | неиспользуемая локальная переменная |
E401 | pycodestyle | несколько импортов в одной строке |
E402 | pycodestyle | импорт не в начале файла |
E501 | pycodestyle | слишком длинная строка |
E701 | pycodestyle | несколько операторов в одной строке |
B008 | flake8-bugbear | потенциально опасный вызов в default argument |
I001 | isort | импорты требуют сортировки |
UP006 | pyupgrade | устаревший синтаксис типов |
D400 | pydocstyle | проблема с окончанием docstring |
D401 | pydocstyle | проблема со стилем docstring |
D417 | pydocstyle | не документирован параметр |
ANN001 | annotations | отсутствует аннотация аргумента |
RUFxxx | Ruff | специфическое правило Ruff |
Это не полный список — Ruff поддерживает сотни правил.
22. Как я рекомендую читать сообщение линтера
Не нужно запоминать сотни кодов.
Лучше читать их как:
F401
│
├── F → семейство
│ Pyflakes
│
└── 401 → конкретное правило
unused-import
Например:
E501
│
├── E → pycodestyle
│
└── 501 → line-too-long
И:
B008
│
├── B → bugbear
│
└── 008 → конкретное правило
После нескольких недель работы коды вроде F401, F821, E501 начинают читаться почти как обычные слова.
23. Линтер ≠ форматтер ≠ type checker
Это принципиально.
В Python-проекте могут одновременно работать:
Python-код
│
┌─────────────┼─────────────┐
↓ ↓ ↓
Linter Formatter Type checker
│ │ │
Ruff Ruff mypy
pyright
Linter
Ищет:
ошибки
подозрительные конструкции
неиспользуемый код
стиль
безопасность
Formatter
Приводит код к единому виду:
пробелы
переносы
отступы
кавычки
структура выражений
Type checker
Проверяет типовую согласованность:
value: int = 'hello'
Например, это уже задача для:
mypy
pyright
а не обычного форматтера.
24. Практическая схема для проекта
Для современного Python-проекта можно построить проверку примерно так:
Исходный код
│
▼
Ruff
┌─────┴─────┐
▼ ▼
lint format
│
▼
исправление
│
▼
Pyright
│
▼
type check
│
▼
tests
Например:
ruff check .
ruff format --check .
pyright
pytest
25. Главное правило
Не стоит воспринимать:
F401
E501
B008
UP006
как случайный набор непонятных цифр.
Это адреса конкретных правил.
Например:
F401
↓
Pyflakes
↓
unused-import
↓
импорт не используется
А:
E501
↓
pycodestyle
↓
line-too-long
↓
строка превышает установленный лимит
И:
# noqa: F403,F401
означает:
для этой конкретной строки:
F403 → подавить
F401 → подавить
остальные правила → продолжать проверять
Именно поэтому # noqa: F403,F401 — это не «магическая пометка», а точечное управление диагностикой линтера.