Дополнительные примеры исправлений
Пример 6: Удаление trailing whitespace (W291)
line_with_spaces = "hello "
spam(eggs, bacon)
result = foo()
line_with_spaces = "hello "
spam(eggs, bacon)
result = foo()
Пример 7: Табуляция в пробелы (W191)
def foo():
if True:
print("tabbed")
def foo():
if True:
print("tabbed")
Пример 8: Пропущенные пробелы после запятой (E231)
foo = (1,2,3)
bar = [a,b,c]
result = func(x,y,z)
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
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
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 умеет не только находить ошибки, но и автоматически их исправлять.
Как работает автоисправление
Ruff может не только находить ошибки, но и автоматически их исправлять. Это одна из ключевых фич, которая выделяет Ruff среди других линтеров. В отличие от традиционных линтеров (flake8, pylint), которые только сообщают о проблемах, Ruff может самостоятельно их устранить.
Механизм работы: Ruff анализирует AST (Abstract Syntax Tree) и для каждого поддерживаемого правила генерирует исправление в виде diff. Исправления применяются к файлу, изменяя только те строки, которые нарушают правило. Важно: Ruff не переформатирует весь файл — он только исправляет конкретные нарушения.
Не все правила поддерживают автоисправление. Информацию о поддержке можно посмотреть в документации или в выводе ruff check --help. На данный момент около 50% правил Ruff имеют автоматические исправления.
--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)
import sys
def main():
print(sys.argv)
Пример 2: Сортировка импортов (I001)
from typing import Optional
import sys
import os
from datetime import datetime
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
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
class Foo:
pass
def bar():
pass
Пример 5: Лишние пробелы внутри скобок (E201, E202)
spam( ham[ 1 ], { eggs: 2 } )
foo( [ 1, 2, 3 ], )
bar( ( 1, 2 ), )
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комментариев, которые уже не нужны.
Примеры небезопасных исправлений
Пример 1: Pyupgrade — f-строки (UP032)
name = "World"
greeting = "Hello, {}!".format(name)
age_str = "Age: {}".format(age)
result = "{}/{}".format(a, b)
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'])
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
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()]
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
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 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 со всеми исправлениями |
Как проверить результаты исправлений
После применения исправлений важно убедиться, что код не сломался. Рекомендуемый workflow:
- Запустить
git diffчтобы увидеть все изменения - Запустить тесты:
pytestилиpython -m unittest - Запустить сам Ruff ещё раз:
ruff check .— должно быть 0 ошибок - Запустить ruff format:
ruff format --check .
# полный цикл проверки после автоисправления
ruff check --fix .
ruff check . # должно быть пусто
ruff format --check . # должно быть пусто
pytest # тесты должны проходить
Урок 2.2: Автоисправление ошибок
5 вопросов