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

Дополнительные примеры исправлений

Пример 6: Удаление trailing whitespace (W291)

До
line_with_spaces = "hello   " 
spam(eggs, bacon)   
result = foo()    
После --fix
line_with_spaces = "hello   "
spam(eggs, bacon)
result = foo()

Пример 7: Табуляция в пробелы (W191)

До
def foo():
	if True:
		print("tabbed")
После --fix
def foo():
    if True:
        print("tabbed")

Пример 8: Пропущенные пробелы после запятой (E231)

До
foo = (1,2,3)
bar = [a,b,c]
result = func(x,y,z)
После --fix
foo = (1, 2, 3)
bar = [a, b, c]
result = func(x, y, z)

Пример 9: Замена type() на isinstance() (SIM132)

До
if type(x) == int:
    pass
if type(y) in (str, bytes):
    pass
После --fix --unsafe-fixes
if isinstance(x, int):
    pass
if isinstance(y, (str, bytes)):
    pass

Пример 10: Упрощение множественных сравнений (SIM202)

До
if a == True:
    pass
if b == False:
    pass
if len(x) != 0:
    pass
После --fix --unsafe-fixes
if a:
    pass
if not b:
    pass
if x:
    pass
🔄

Многоэтапное исправление

Некоторые исправления могут создавать новые нарушения, которые требуют повторного запуска. Особенно это касается небезопасных исправлений, которые меняют структуру кода. Рекомендуется запускать ruff check --fix несколько раз до полной стабилизации:

# Шаг 1: безопасные исправления
ruff check --fix .
# Шаг 2: проверить, остались ли ошибки
ruff check --statistics .
# Шаг 3: если есть — снова --fix
ruff check --fix .
# Повторять пока --statistics не покажет 0 нарушений

# Или в одну строку (bash):
while ruff check --fix --quiet . | grep -q "fixed"; do :; done

Пример, когда нужно несколько проходов:

# Исходный код:
import os, sys

# После первого --fix (сортировка импортов):
import os
import sys

# После второго --fix (пробелы вокруг импортов):
import os
import sys

# И так далее — каждый проход может исправить то, что появилось после предыдущего
⚡

Производительность автоисправления

Ruff известен своей скоростью. Автоисправление практически не увеличивает время проверки, так как исправления генерируются во время того же AST-анализа. Вот сравнение производительности:

Инструмент Время (1000 файлов) Фичи
ruff check --fix ~0.3 сек Линтинг + автоисправление
autoflake8 ~5 сек Только удаление импортов
isort + black ~2 сек Только сортировка и форматирование
pylint + autopep8 ~15 сек Линтинг + базовое исправление

Автоматическое исправление

Ruff умеет не только находить ошибки, но и автоматически их исправлять.

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

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

Ruff может не только находить ошибки, но и автоматически их исправлять. Это одна из ключевых фич, которая выделяет Ruff среди других линтеров. В отличие от традиционных линтеров (flake8, pylint), которые только сообщают о проблемах, Ruff может самостоятельно их устранить.

Механизм работы: Ruff анализирует AST (Abstract Syntax Tree) и для каждого поддерживаемого правила генерирует исправление в виде diff. Исправления применяются к файлу, изменяя только те строки, которые нарушают правило. Важно: Ruff не переформатирует весь файл — он только исправляет конкретные нарушения.

Не все правила поддерживают автоисправление. Информацию о поддержке можно посмотреть в документации или в выводе ruff check --help. На данный момент около 50% правил Ruff имеют автоматические исправления.

💡 Ruff применяет исправления на уровне AST, а не через regex-замены. Это значит, что исправления семантически корректны и не нарушают структуру кода.
🔧

--fix — безопасное исправление

Флаг --fix включает применение безопасных исправлений — тех, которые гарантированно не меняют поведение кода. Если для какого-то правила нет безопасного исправления или исправление небезопасно, Ruff пропустит его (если не указан --unsafe-fixes).

# исправить все безопасные ошибки в проекте
ruff check --fix .

# исправить в конкретном файле
ruff check --fix main.py

# исправить только определённые категории
ruff check --select I --fix .      # только сортировка импортов
ruff check --select E,W --fix .    # только пробелы и стиль
ruff check --select F --fix .      # только неиспользуемый код

# исправить и показать оставшиеся ошибки
ruff check --fix --select ALL --ignore E501 .

Что считается безопасным исправлением:

  • Удаление неиспользуемого импорта (F401)
  • Удаление неиспользуемой переменной (F841)
  • Расстановка пробелов вокруг операторов (E225, E226, E227)
  • Удаление лишних пробелов (E201, E202, E203)
  • Добавление пустых строк между функциями/классами (E302, E303)
  • Сортировка импортов (I001)
  • Замена табуляции на пробелы (W191)
  • Удаление trailing whitespace (W291)
⚠️ Всегда проверяй изменения через git diff после --fix, особенно в первый раз на новом проекте. Хотя исправления безопасны, на больших проектах может быть неожиданно много изменений.
✅

Примеры безопасных исправлений

Пример 1: Удаление неиспользуемого импорта (F401)

До
import os
import sys
import json

def main():
    print(sys.argv)
После --fix
import sys

def main():
    print(sys.argv)

Пример 2: Сортировка импортов (I001)

До
from typing import Optional
import sys
import os
from datetime import datetime
После --fix
import os
import sys
from datetime import datetime
from typing import Optional

Пример 3: Пробелы вокруг операторов (E225)

До
x = 1+2*3-4/5
y = foo(1,2,3)
z = x==y
result = (x+y)*z
После --fix
x = 1 + 2 * 3 - 4 / 5
y = foo(1, 2, 3)
z = x == y
result = (x + y) * z

Пример 4: Пустые строки после определения (E302)

До
class Foo:
    pass
def bar():
    pass
После --fix
class Foo:
    pass

def bar():
    pass

Пример 5: Лишние пробелы внутри скобок (E201, E202)

До
spam( ham[ 1 ], { eggs: 2 } )
foo( [ 1, 2, 3 ], )
bar( ( 1, 2 ), )
После --fix
spam(ham[1], {eggs: 2})
foo([1, 2, 3],)
bar((1, 2),)
⚡

--unsafe-fixes — небезопасные исправления

Флаг --unsafe-fixes включает исправления, которые теоретически могут изменить поведение кода. По умолчанию Ruff применяет только безопасные исправления. Чтобы применить все возможные исправления (включая небезопасные), используйте оба флага вместе:

# применить и безопасные, и небезопасные исправления
ruff check --fix --unsafe-fixes .

# можно настроить в конфиге
# pyproject.toml
# [tool.ruff.lint]
# unsafe-fixes = true

Категории небезопасных исправлений:

  • UP (pyupgrade) — замена устаревшего синтаксиса. Например, замена .format() на f-строки, замена set([1,2,3]) на {1,2,3}, замена type: foo на аннотации. Теоретически может изменить порядок вычислений.
  • SIM (flake8-simplify) — упрощение выражений. Например, замена if x: return True на return x. Может изменить поведение при не-strict булевых значениях.
  • PT (flake8-pytest-style) — замена self.assertEqual(a, b) на assert a == b. Меняет сообщение об ошибке при падении теста.
  • RUF100 — удаление избыточных # noqa комментариев, которые уже не нужны.
⚠️ Будьте осторожны с --unsafe-fixes! Всегда проверяйте diff перед применением. Особенно в проектах, где важна обратная совместимость или где тесты не имеют 100% покрытия.
🔄

Примеры небезопасных исправлений

Пример 1: Pyupgrade — f-строки (UP032)

До
name = "World"
greeting = "Hello, {}!".format(name)
age_str = "Age: {}".format(age)
result = "{}/{}".format(a, b)
После --fix --unsafe-fixes
name = "World"
greeting = f"Hello, {name}!"
age_str = f"Age: {age}"
result = f"{a}/{b}"

Пример 2: Pyupgrade — set literal (UP018)

До
empty = set()
items = set([1, 2, 3])
unique = set(['a', 'b', 'c'])
После --fix --unsafe-fixes
empty = set()
items = {1, 2, 3}
unique = {'a', 'b', 'c'}
# set() остаётся — нет литерала для пустого множества

Пример 3: Simplify — упрощение условия (SIM103)

До
def is_positive(x):
    if x > 0:
        return True
    return False

def has_items(lst):
    if len(lst) > 0:
        return True
    return False
После --fix --unsafe-fixes
def is_positive(x):
    return x > 0

def has_items(lst):
    return len(lst) > 0

Пример 4: Simplify — dict comprehension (SIM118)

До
result = {key: value for key, value in some_dict.items()}
keys = [key for key in some_dict.keys()]
После --fix --unsafe-fixes
result = dict(some_dict)
keys = list(some_dict)

Пример 5: Pyupgrade — type annotations (UP006, UP035)

До
from typing import List, Dict, Optional, Union

def process(items: List[str]) -> Optional[Dict[str, int]]:
    result: Dict[str, int] = {}
    return result
После --fix --unsafe-fixes (если target-version >= py39)
from typing import Optional
# List, Dict, Union больше не нужны — используем встроенные

def process(items: list[str]) -> dict[str, int] | None:
    result: dict[str, int] = {}
    return result
👁️

--diff — предпросмотр исправлений

Флаг --diff показывает unified diff всех исправлений, которые будут применены, без фактического изменения файлов. Это безопасный способ проверить, что именно изменит Ruff, перед тем как применять исправления. Рекомендуется использовать перед --fix, особенно с --unsafe-fixes.

# показать что будет изменено без применения
ruff check --diff .

# показать diff только для определённых правил
ruff check --select I --diff .

# показать diff для небезопасных исправлений
ruff check --fix --unsafe-fixes --diff .

# сохранить diff в файл для просмотра
ruff check --diff . > changes.patch

# применить потом через
patch -p1 < changes.patch

Пример вывода --diff:

--- a/src/main.py
+++ b/src/main.py
@@ -1,2 +1,2 @@
-import os
 import sys
+import sys

-def foo(x,y):
-    return x+y
+def foo(x, y):
+    return x + y
💡 --diff можно комбинировать с --watch для непрерывного предпросмотра в процессе разработки.
🔒

fixable / unfixable — управление исправлениями

Ruff позволяет контролировать, какие правила могут применять исправления. Это удобно, когда вы хотите видеть определённые ошибки, но не хотите, чтобы они автоматически исправлялись.

Настройка через конфигурационный файл:

# pyproject.toml
[tool.ruff]
# все правила, помеченные как fixable, могут исправляться
fixable = ["ALL"]

# явно запретить исправление некоторых правил
[tool.ruff.lint]
unfixable = ["E501", "SIM108", "D"]

# можно указать только определённые fixable
[tool.ruff]
fixable = ["F", "I", "E", "W"]
# остальные правила не будут исправляться

В CLI:

# исправлять только F и I
ruff check --fix --select F,I .

# в конфиге можно настроить так:
[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "B", "S", "SIM"]
fixable = ["I"]  # только сортировка импортов
# остальные правила — только отчёт, без исправлений

Таблица: fixable-статусы популярных правил

Правило Fixable Safety Описание
F401 ✅ Safe Удаление неиспользуемого импорта
F841 ✅ Safe Удаление неиспользуемой переменной
I001 ✅ Safe Сортировка импортов
E225 ✅ Safe Пробелы вокруг операторов
W291 ✅ Safe Удаление trailing whitespace
UP032 ✅ Unsafe Замена .format() на f-строки
SIM108 ✅ Unsafe Замена условного оператора на тернарный
E501 ❌ — Слишком длинная строка (нет auto-fix)
N801 ❌ — Неправильное имя класса (нет auto-fix)
D100 ❌ — Отсутствует docstring (нет auto-fix)
💡 Чтобы узнать, какие правила имеют исправления в вашей версии Ruff, выполните: ruff rule --all или посмотрите ruff check --help в секции "Available rules".
🛡️

Категории безопасности исправлений

Каждое исправление в Ruff относится к одной из двух категорий безопасности:

Категория Флаг Гарантии Примеры
Safe (безопасные) --fix Не меняют семантику кода. AST и runtime поведение идентичны. Удаление импортов, пробелы, сортировка
Unsafe (небезопасные) --unsafe-fixes Могут изменить поведение. Требуют проверки. f-строки, упрощение условий, замена typing

Почему некоторые исправления считаются небезопасными?

Основная причина — консервативный подход Ruff. Если есть хотя бы теоретическая вероятность изменения поведения, исправление помечается как unsafe. Например, замена .format() на f-строки безопасна в 99.9% случаев, но если в .format() передавались сложные выражения с побочными эффектами, порядок вычислений может измениться:

# Безопасно, но Ruff помечает как unsafe из-за потенциальных edge case:
def get_value():
    print("side effect!")
    return 42

# .format() — аргументы вычисляются слева направо
result = "{} {}".format(get_value(), get_value())
# f-строки — то же самое, но в сложных случаях порядок может отличаться
result = f"{get_value()} {get_value()}"
🔧 Если у вас хорошее тестовое покрытие, можно безопасно использовать --unsafe-fixes в CI. Тесты поймают любые случайные изменения поведения.
📦

Пакетное исправление больших проектов

При работе с большими проектами (тысячи файлов) рекомендуется поэтапный подход:

# Шаг 1: Посмотреть статистику
ruff check --statistics .

# Шаг 2: Исправить только сортировку импортов
ruff check --select I --fix .

# Шаг 3: Исправить пробелы
ruff check --select E,W --fix .

# Шаг 4: Удалить неиспользуемые импорты (безопасно)
ruff check --select F --fix .

# Шаг 5: Проверить, что осталось
ruff check --statistics .

# Шаг 6: Если всё хорошо — небезопасные исправления
ruff check --fix --unsafe-fixes --diff .  # сначала посмотреть
ruff check --fix --unsafe-fixes .         # потом применить
⚠️ На больших проектах всегда работайте в отдельной ветке! git checkout -b fix/ruff-autofix перед применением массовых исправлений.
🤖

Автоисправление в CI

В CI можно настроить автоматическое исправление с созданием PR. Пример для GitHub Actions:

# .github/workflows/ruff-fix.yml
name: Ruff auto-fix
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  ruff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/ruff-action@v2
        with:
          args: check --fix --select I,F,E
      
      # если файлы изменились — создаём commit
      - name: Commit changes
        run: |
          git config user.name "Ruff Bot"
          git config user.email "ruff-bot@example.com"
          git add -A
          git diff --quiet || git commit -m "style: автоисправления Ruff"
          git push

Или можно просто проверять, что код не содержит ошибок:

# Только проверка, без исправлений
- name: Run Ruff check
  run: ruff check --select E,F,I,N,UP,B .

Второй подход предпочтительнее для CI, так как он явно сообщает об ошибках, а не молча исправляет их.

📝

Шпаргалка: все флаги автоисправления

Команда Назначение
ruff check --fix . Безопасные исправления
ruff check --fix --unsafe-fixes . Все исправления (безопасные + небезопасные)
ruff check --diff . Предпросмотр без применения
ruff check --select I --fix . Только сортировка импортов
ruff check --select E,W --fix . Только пробелы и стиль
ruff check --select F --fix --unsafe-fixes . Только Pyflakes со всеми исправлениями
📖 Документация: docs.astral.sh/ruff/linter/#fix
✅

Как проверить результаты исправлений

После применения исправлений важно убедиться, что код не сломался. Рекомендуемый workflow:

  1. Запустить git diff чтобы увидеть все изменения
  2. Запустить тесты: pytest или python -m unittest
  3. Запустить сам Ruff ещё раз: ruff check . — должно быть 0 ошибок
  4. Запустить ruff format: ruff format --check .
# полный цикл проверки после автоисправления
ruff check --fix .
ruff check .          # должно быть пусто
ruff format --check . # должно быть пусто
pytest                # тесты должны проходить

Урок 2.2: Автоисправление ошибок

5 вопросов