Перейти к содержимому
davidka.net > 💻 🧠 Код 1001 > 📑 Шпаргалки > 🐍 Шпаргалки Python > Коды линтеров Python: как их читать и понимать

Коды линтеров Python: как их читать и понимать

  • автор:

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 сам по себе не имеет единого набора кодов линтера.

Коды принадлежат конкретным инструментам или их наборам правил.

Например:

КодИсточникНазначение
F401Pyflakesнеиспользуемый импорт
E501pycodestyleслишком длинная строка
E302pycodestyleнеправильное количество пустых строк
B008flake8-bugbearпотенциально проблемный вызов функции в аргументе по умолчанию
UP006pyupgradeустаревший синтаксис аннотации типа
I001isortнеправильный порядок импортов
D401pydocstyleпроблема с первой строкой docstring
ANN001flake8-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. Полезная карта кодов

Для повседневной разработки достаточно сначала запомнить следующие:

КодИсточник / семействоЧто обычно означает
F401Pyflakesнеиспользуемый импорт
F403Pyflakesimport *
F405Pyflakesвозможное неопределённое имя из import *
F821Pyflakesнеопределённое имя
F841Pyflakesнеиспользуемая локальная переменная
E401pycodestyleнесколько импортов в одной строке
E402pycodestyleимпорт не в начале файла
E501pycodestyleслишком длинная строка
E701pycodestyleнесколько операторов в одной строке
B008flake8-bugbearпотенциально опасный вызов в default argument
I001isortимпорты требуют сортировки
UP006pyupgradeустаревший синтаксис типов
D400pydocstyleпроблема с окончанием docstring
D401pydocstyleпроблема со стилем docstring
D417pydocstyleне документирован параметр
ANN001annotationsотсутствует аннотация аргумента
RUFxxxRuffспецифическое правило 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 — это не «магическая пометка», а точечное управление диагностикой линтера.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *